summaryrefslogtreecommitdiffstats
path: root/netwerk/base
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /netwerk/base
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'netwerk/base')
-rw-r--r--netwerk/base/ADivertableParentChannel.h45
-rw-r--r--netwerk/base/ARefBase.h32
-rw-r--r--netwerk/base/ArrayBufferInputStream.cpp120
-rw-r--r--netwerk/base/ArrayBufferInputStream.h38
-rw-r--r--netwerk/base/AutoClose.h77
-rw-r--r--netwerk/base/BackgroundFileSaver.cpp1273
-rw-r--r--netwerk/base/BackgroundFileSaver.h418
-rw-r--r--netwerk/base/CaptivePortalService.cpp366
-rw-r--r--netwerk/base/CaptivePortalService.h70
-rw-r--r--netwerk/base/ChannelDiverterChild.cpp27
-rw-r--r--netwerk/base/ChannelDiverterChild.h26
-rw-r--r--netwerk/base/ChannelDiverterParent.cpp75
-rw-r--r--netwerk/base/ChannelDiverterParent.h40
-rw-r--r--netwerk/base/Dashboard.cpp921
-rw-r--r--netwerk/base/Dashboard.h105
-rw-r--r--netwerk/base/DashboardTypes.h63
-rw-r--r--netwerk/base/EventTokenBucket.cpp462
-rw-r--r--netwerk/base/EventTokenBucket.h149
-rw-r--r--netwerk/base/LoadContextInfo.cpp181
-rw-r--r--netwerk/base/LoadContextInfo.h61
-rw-r--r--netwerk/base/LoadInfo.cpp925
-rw-r--r--netwerk/base/LoadInfo.h163
-rw-r--r--netwerk/base/LoadTainting.h43
-rw-r--r--netwerk/base/MemoryDownloader.cpp92
-rw-r--r--netwerk/base/MemoryDownloader.h65
-rw-r--r--netwerk/base/NetStatistics.h100
-rw-r--r--netwerk/base/NetUtil.jsm461
-rw-r--r--netwerk/base/NetworkActivityMonitor.cpp300
-rw-r--r--netwerk/base/NetworkActivityMonitor.h46
-rw-r--r--netwerk/base/NetworkInfoServiceCocoa.cpp104
-rw-r--r--netwerk/base/NetworkInfoServiceImpl.h18
-rw-r--r--netwerk/base/NetworkInfoServiceLinux.cpp104
-rw-r--r--netwerk/base/NetworkInfoServiceWindows.cpp60
-rw-r--r--netwerk/base/PollableEvent.cpp347
-rw-r--r--netwerk/base/PollableEvent.h38
-rw-r--r--netwerk/base/Predictor.cpp2550
-rw-r--r--netwerk/base/Predictor.h480
-rw-r--r--netwerk/base/PrivateBrowsingChannel.h132
-rw-r--r--netwerk/base/ProxyAutoConfig.cpp1015
-rw-r--r--netwerk/base/ProxyAutoConfig.h104
-rw-r--r--netwerk/base/RedirectChannelRegistrar.cpp87
-rw-r--r--netwerk/base/RedirectChannelRegistrar.h44
-rw-r--r--netwerk/base/ReferrerPolicy.h186
-rw-r--r--netwerk/base/RequestContextService.cpp217
-rw-r--r--netwerk/base/RequestContextService.h47
-rw-r--r--netwerk/base/ShutdownLayer.cpp80
-rw-r--r--netwerk/base/ShutdownLayer.h22
-rw-r--r--netwerk/base/SimpleBuffer.cpp95
-rw-r--r--netwerk/base/SimpleBuffer.h57
-rw-r--r--netwerk/base/StreamingProtocolService.cpp72
-rw-r--r--netwerk/base/StreamingProtocolService.h36
-rw-r--r--netwerk/base/TLSServerSocket.cpp515
-rw-r--r--netwerk/base/TLSServerSocket.h81
-rw-r--r--netwerk/base/ThrottleQueue.cpp392
-rw-r--r--netwerk/base/ThrottleQueue.h65
-rw-r--r--netwerk/base/Tickler.cpp277
-rw-r--r--netwerk/base/Tickler.h131
-rw-r--r--netwerk/base/moz.build316
-rw-r--r--netwerk/base/mozIThirdPartyUtil.idl167
-rw-r--r--netwerk/base/netCore.h14
-rw-r--r--netwerk/base/nsASocketHandler.h96
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.cpp288
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.h129
-rw-r--r--netwerk/base/nsAsyncStreamCopier.cpp419
-rw-r--r--netwerk/base/nsAsyncStreamCopier.h83
-rw-r--r--netwerk/base/nsAuthInformationHolder.cpp74
-rw-r--r--netwerk/base/nsAuthInformationHolder.h48
-rw-r--r--netwerk/base/nsBase64Encoder.cpp67
-rw-r--r--netwerk/base/nsBase64Encoder.h32
-rw-r--r--netwerk/base/nsBaseChannel.cpp937
-rw-r--r--netwerk/base/nsBaseChannel.h300
-rw-r--r--netwerk/base/nsBaseContentStream.cpp137
-rw-r--r--netwerk/base/nsBaseContentStream.h82
-rw-r--r--netwerk/base/nsBufferedStreams.cpp832
-rw-r--r--netwerk/base/nsBufferedStreams.h122
-rw-r--r--netwerk/base/nsChannelClassifier.cpp696
-rw-r--r--netwerk/base/nsChannelClassifier.h65
-rw-r--r--netwerk/base/nsDNSPrefetch.cpp106
-rw-r--r--netwerk/base/nsDNSPrefetch.h53
-rw-r--r--netwerk/base/nsDirectoryIndexStream.cpp359
-rw-r--r--netwerk/base/nsDirectoryIndexStream.h49
-rw-r--r--netwerk/base/nsDownloader.cpp114
-rw-r--r--netwerk/base/nsDownloader.h40
-rw-r--r--netwerk/base/nsFileStreams.cpp1153
-rw-r--r--netwerk/base/nsFileStreams.h333
-rw-r--r--netwerk/base/nsIApplicationCache.idl205
-rw-r--r--netwerk/base/nsIApplicationCacheChannel.idl57
-rw-r--r--netwerk/base/nsIApplicationCacheContainer.idl19
-rw-r--r--netwerk/base/nsIApplicationCacheService.idl110
-rw-r--r--netwerk/base/nsIArrayBufferInputStream.idl26
-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.idl118
-rw-r--r--netwerk/base/nsIAuthModule.idl145
-rw-r--r--netwerk/base/nsIAuthPrompt.idl80
-rw-r--r--netwerk/base/nsIAuthPrompt2.idl103
-rw-r--r--netwerk/base/nsIAuthPromptAdapterFactory.idl22
-rw-r--r--netwerk/base/nsIAuthPromptCallback.idl44
-rw-r--r--netwerk/base/nsIAuthPromptProvider.idl34
-rw-r--r--netwerk/base/nsIBackgroundFileSaver.idl182
-rw-r--r--netwerk/base/nsIBrowserSearchService.idl530
-rw-r--r--netwerk/base/nsIBufferedStreams.idl37
-rw-r--r--netwerk/base/nsIByteRangeRequest.idl27
-rw-r--r--netwerk/base/nsICacheInfoChannel.idl84
-rw-r--r--netwerk/base/nsICachingChannel.idl129
-rw-r--r--netwerk/base/nsICancelable.idl24
-rw-r--r--netwerk/base/nsICaptivePortalService.idl59
-rw-r--r--netwerk/base/nsIChannel.idl357
-rw-r--r--netwerk/base/nsIChannelEventSink.idl101
-rw-r--r--netwerk/base/nsIChannelWithDivertableParentListener.idl46
-rw-r--r--netwerk/base/nsIChildChannel.idl35
-rw-r--r--netwerk/base/nsIClassOfService.idl47
-rw-r--r--netwerk/base/nsIContentSniffer.idl35
-rw-r--r--netwerk/base/nsICryptoFIPSInfo.idl15
-rw-r--r--netwerk/base/nsICryptoHMAC.idl108
-rw-r--r--netwerk/base/nsICryptoHash.idl105
-rw-r--r--netwerk/base/nsIDashboard.idl54
-rw-r--r--netwerk/base/nsIDashboardEventNotifier.idl23
-rw-r--r--netwerk/base/nsIDeprecationWarner.idl23
-rw-r--r--netwerk/base/nsIDivertableChannel.idl78
-rw-r--r--netwerk/base/nsIDownloader.idl51
-rw-r--r--netwerk/base/nsIEncodedChannel.idl55
-rw-r--r--netwerk/base/nsIExternalProtocolHandler.idl17
-rw-r--r--netwerk/base/nsIFileStreams.idl237
-rw-r--r--netwerk/base/nsIFileURL.idl29
-rw-r--r--netwerk/base/nsIForcePendingChannel.idl22
-rw-r--r--netwerk/base/nsIFormPOSTActionChannel.idl17
-rw-r--r--netwerk/base/nsIHttpAuthenticatorCallback.idl31
-rw-r--r--netwerk/base/nsIHttpPushListener.idl36
-rw-r--r--netwerk/base/nsIIOService.idl218
-rw-r--r--netwerk/base/nsIIOService2.idl82
-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.idl73
-rw-r--r--netwerk/base/nsILoadContextInfo.idl89
-rw-r--r--netwerk/base/nsILoadGroup.idl104
-rw-r--r--netwerk/base/nsILoadGroupChild.idl42
-rw-r--r--netwerk/base/nsILoadInfo.idl732
-rw-r--r--netwerk/base/nsIMIMEInputStream.idl47
-rw-r--r--netwerk/base/nsIMultiPartChannel.idl35
-rw-r--r--netwerk/base/nsINSSErrorsService.idl69
-rw-r--r--netwerk/base/nsINestedURI.idl46
-rw-r--r--netwerk/base/nsINetAddr.idl88
-rw-r--r--netwerk/base/nsINetUtil.idl230
-rw-r--r--netwerk/base/nsINetworkInfoService.idl57
-rw-r--r--netwerk/base/nsINetworkInterceptController.idl144
-rw-r--r--netwerk/base/nsINetworkLinkService.idl108
-rw-r--r--netwerk/base/nsINetworkPredictor.idl165
-rw-r--r--netwerk/base/nsINetworkPredictorVerifier.idl40
-rw-r--r--netwerk/base/nsINetworkProperties.idl18
-rw-r--r--netwerk/base/nsINullChannel.idl16
-rw-r--r--netwerk/base/nsIOService.cpp1880
-rw-r--r--netwerk/base/nsIOService.h203
-rw-r--r--netwerk/base/nsIParentChannel.idl41
-rw-r--r--netwerk/base/nsIParentRedirectingChannel.idl46
-rw-r--r--netwerk/base/nsIPermission.idl77
-rw-r--r--netwerk/base/nsIPermissionManager.idl271
-rw-r--r--netwerk/base/nsIPrivateBrowsingChannel.idl58
-rw-r--r--netwerk/base/nsIProgressEventSink.idl80
-rw-r--r--netwerk/base/nsIPrompt.idl97
-rw-r--r--netwerk/base/nsIProtocolHandler.idl321
-rw-r--r--netwerk/base/nsIProtocolProxyCallback.idl42
-rw-r--r--netwerk/base/nsIProtocolProxyFilter.idl74
-rw-r--r--netwerk/base/nsIProtocolProxyService.idl281
-rw-r--r--netwerk/base/nsIProtocolProxyService2.idl30
-rw-r--r--netwerk/base/nsIProxiedChannel.idl27
-rw-r--r--netwerk/base/nsIProxiedProtocolHandler.idl55
-rw-r--r--netwerk/base/nsIProxyInfo.idl89
-rw-r--r--netwerk/base/nsIRandomGenerator.idl24
-rw-r--r--netwerk/base/nsIRedirectChannelRegistrar.idl72
-rw-r--r--netwerk/base/nsIRedirectResultListener.idl22
-rw-r--r--netwerk/base/nsIRequest.idl217
-rw-r--r--netwerk/base/nsIRequestContext.idl95
-rw-r--r--netwerk/base/nsIRequestObserver.idl41
-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.idl24
-rw-r--r--netwerk/base/nsISecurityEventSink.idl26
-rw-r--r--netwerk/base/nsISecurityInfoProvider.idl21
-rw-r--r--netwerk/base/nsISensitiveInfoHiddenURI.idl17
-rw-r--r--netwerk/base/nsISerializationHelper.idl29
-rw-r--r--netwerk/base/nsIServerSocket.idl237
-rw-r--r--netwerk/base/nsISimpleStreamListener.idl25
-rw-r--r--netwerk/base/nsISocketFilter.idl53
-rw-r--r--netwerk/base/nsISocketTransport.idl256
-rw-r--r--netwerk/base/nsISocketTransportService.idl135
-rw-r--r--netwerk/base/nsISpeculativeConnect.idl77
-rw-r--r--netwerk/base/nsIStandardURL.idl80
-rw-r--r--netwerk/base/nsIStreamListener.idl40
-rw-r--r--netwerk/base/nsIStreamListenerTee.idl50
-rw-r--r--netwerk/base/nsIStreamLoader.idl77
-rw-r--r--netwerk/base/nsIStreamTransportService.idl68
-rw-r--r--netwerk/base/nsIStreamingProtocolController.idl186
-rw-r--r--netwerk/base/nsIStreamingProtocolService.idl35
-rw-r--r--netwerk/base/nsISyncStreamListener.idl19
-rw-r--r--netwerk/base/nsISystemProxySettings.idl42
-rw-r--r--netwerk/base/nsITLSServerSocket.idl201
-rw-r--r--netwerk/base/nsIThreadRetargetableRequest.idl35
-rw-r--r--netwerk/base/nsIThreadRetargetableStreamListener.idl33
-rw-r--r--netwerk/base/nsIThrottledInputChannel.idl80
-rw-r--r--netwerk/base/nsITimedChannel.idl80
-rw-r--r--netwerk/base/nsITraceableChannel.idl34
-rw-r--r--netwerk/base/nsITransport.idl163
-rw-r--r--netwerk/base/nsIUDPSocket.idl353
-rw-r--r--netwerk/base/nsIURI.idl290
-rw-r--r--netwerk/base/nsIURIClassifier.idl65
-rw-r--r--netwerk/base/nsIURIWithBlobImpl.idl20
-rw-r--r--netwerk/base/nsIURIWithPrincipal.idl27
-rw-r--r--netwerk/base/nsIURIWithQuery.idl30
-rw-r--r--netwerk/base/nsIURL.idl122
-rw-r--r--netwerk/base/nsIURLParser.idl98
-rw-r--r--netwerk/base/nsIUnicharStreamLoader.idl88
-rw-r--r--netwerk/base/nsIUploadChannel.idl56
-rw-r--r--netwerk/base/nsIUploadChannel2.idl67
-rw-r--r--netwerk/base/nsIncrementalDownload.cpp936
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.cpp214
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.h54
-rw-r--r--netwerk/base/nsInputStreamChannel.cpp112
-rw-r--r--netwerk/base/nsInputStreamChannel.h47
-rw-r--r--netwerk/base/nsInputStreamPump.cpp766
-rw-r--r--netwerk/base/nsInputStreamPump.h105
-rw-r--r--netwerk/base/nsLoadGroup.cpp1087
-rw-r--r--netwerk/base/nsLoadGroup.h105
-rw-r--r--netwerk/base/nsMIMEInputStream.cpp390
-rw-r--r--netwerk/base/nsMIMEInputStream.h29
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.cpp390
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.h106
-rw-r--r--netwerk/base/nsNetAddr.cpp152
-rw-r--r--netwerk/base/nsNetAddr.h31
-rw-r--r--netwerk/base/nsNetSegmentUtils.h23
-rw-r--r--netwerk/base/nsNetUtil.cpp2459
-rw-r--r--netwerk/base/nsNetUtil.h1000
-rw-r--r--netwerk/base/nsNetUtilInlines.h395
-rw-r--r--netwerk/base/nsNetworkInfoService.cpp113
-rw-r--r--netwerk/base/nsNetworkInfoService.h40
-rw-r--r--netwerk/base/nsPACMan.cpp769
-rw-r--r--netwerk/base/nsPACMan.h248
-rw-r--r--netwerk/base/nsPILoadGroupInternal.idl28
-rw-r--r--netwerk/base/nsPISocketTransportService.idl61
-rw-r--r--netwerk/base/nsPreloadedStream.cpp153
-rw-r--r--netwerk/base/nsPreloadedStream.h52
-rw-r--r--netwerk/base/nsProtocolProxyService.cpp2146
-rw-r--r--netwerk/base/nsProtocolProxyService.h414
-rw-r--r--netwerk/base/nsProxyInfo.cpp126
-rw-r--r--netwerk/base/nsProxyInfo.h82
-rw-r--r--netwerk/base/nsReadLine.h134
-rw-r--r--netwerk/base/nsRequestObserverProxy.cpp195
-rw-r--r--netwerk/base/nsRequestObserverProxy.h58
-rw-r--r--netwerk/base/nsSecCheckWrapChannel.cpp196
-rw-r--r--netwerk/base/nsSecCheckWrapChannel.h106
-rw-r--r--netwerk/base/nsSerializationHelper.cpp73
-rw-r--r--netwerk/base/nsSerializationHelper.h38
-rw-r--r--netwerk/base/nsServerSocket.cpp560
-rw-r--r--netwerk/base/nsServerSocket.h67
-rw-r--r--netwerk/base/nsSimpleNestedURI.cpp190
-rw-r--r--netwerk/base/nsSimpleNestedURI.h75
-rw-r--r--netwerk/base/nsSimpleStreamListener.cpp83
-rw-r--r--netwerk/base/nsSimpleStreamListener.h36
-rw-r--r--netwerk/base/nsSimpleURI.cpp847
-rw-r--r--netwerk/base/nsSimpleURI.h110
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3245
-rw-r--r--netwerk/base/nsSocketTransport2.h471
-rw-r--r--netwerk/base/nsSocketTransportService2.cpp1627
-rw-r--r--netwerk/base/nsSocketTransportService2.h282
-rw-r--r--netwerk/base/nsStandardURL.cpp3619
-rw-r--r--netwerk/base/nsStandardURL.h405
-rw-r--r--netwerk/base/nsStreamListenerTee.cpp142
-rw-r--r--netwerk/base/nsStreamListenerTee.h43
-rw-r--r--netwerk/base/nsStreamListenerWrapper.cpp32
-rw-r--r--netwerk/base/nsStreamListenerWrapper.h43
-rw-r--r--netwerk/base/nsStreamLoader.cpp169
-rw-r--r--netwerk/base/nsStreamLoader.h58
-rw-r--r--netwerk/base/nsStreamTransportService.cpp563
-rw-r--r--netwerk/base/nsStreamTransportService.h48
-rw-r--r--netwerk/base/nsSyncStreamListener.cpp181
-rw-r--r--netwerk/base/nsSyncStreamListener.h45
-rw-r--r--netwerk/base/nsTemporaryFileInputStream.cpp252
-rw-r--r--netwerk/base/nsTemporaryFileInputStream.h64
-rw-r--r--netwerk/base/nsTransportUtils.cpp142
-rw-r--r--netwerk/base/nsTransportUtils.h26
-rw-r--r--netwerk/base/nsUDPSocket.cpp1613
-rw-r--r--netwerk/base/nsUDPSocket.h131
-rw-r--r--netwerk/base/nsURIHashKey.h61
-rw-r--r--netwerk/base/nsURLHelper.cpp1168
-rw-r--r--netwerk/base/nsURLHelper.h250
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp216
-rw-r--r--netwerk/base/nsURLHelperUnix.cpp112
-rw-r--r--netwerk/base/nsURLHelperWin.cpp117
-rw-r--r--netwerk/base/nsURLParsers.cpp702
-rw-r--r--netwerk/base/nsURLParsers.h124
-rw-r--r--netwerk/base/nsUnicharStreamLoader.cpp250
-rw-r--r--netwerk/base/nsUnicharStreamLoader.h60
-rw-r--r--netwerk/base/rust-url-capi/.gitignore2
-rw-r--r--netwerk/base/rust-url-capi/Cargo.toml19
-rw-r--r--netwerk/base/rust-url-capi/src/error_mapping.rs68
-rw-r--r--netwerk/base/rust-url-capi/src/lib.rs477
-rw-r--r--netwerk/base/rust-url-capi/src/rust-url-capi.h45
-rw-r--r--netwerk/base/rust-url-capi/src/string_utils.rs57
-rw-r--r--netwerk/base/rust-url-capi/test/Makefile4
-rw-r--r--netwerk/base/rust-url-capi/test/test.cpp141
-rw-r--r--netwerk/base/security-prefs.js119
304 files changed, 67756 insertions, 0 deletions
diff --git a/netwerk/base/ADivertableParentChannel.h b/netwerk/base/ADivertableParentChannel.h
new file mode 100644
index 000000000..2102d3d83
--- /dev/null
+++ b/netwerk/base/ADivertableParentChannel.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _adivertablechannelparent_h_
+#define _adivertablechannelparent_h_
+
+#include "nsISupports.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+// To be implemented by a channel's parent actors, e.g. HttpChannelParent
+// and FTPChannelParent. Used by ChannelDiverterParent to divert
+// nsIStreamListener callbacks from the child process to a new
+// listener in the parent process.
+class ADivertableParentChannel : public nsISupports
+{
+public:
+ // Called by ChannelDiverterParent::DivertTo(nsIStreamListener*).
+ // The listener should now be used to received nsIStreamListener callbacks,
+ // i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been
+ // passed to AsyncOpen for the channel. A reference to the listener will be
+ // added and kept until OnStopRequest has completed.
+ virtual void DivertTo(nsIStreamListener *aListener) = 0;
+
+ // Called to suspend parent channel in ChannelDiverterParent constructor.
+ virtual nsresult SuspendForDiversion() = 0;
+
+ // While messages are diverted back from the child to the parent calls to
+ // suspend/resume the channel must also suspend/resume the message diversion.
+ // These two functions will be called by nsHttpChannel and nsFtpChannel
+ // Suspend()/Resume() functions.
+ virtual nsresult SuspendMessageDiversion() = 0;
+ virtual nsresult ResumeMessageDiversion() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h
new file mode 100644
index 000000000..0b2726c43
--- /dev/null
+++ b/netwerk/base/ARefBase.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ARefBase_h
+#define mozilla_net_ARefBase_h
+
+#include "nscore.h"
+
+namespace mozilla { namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase
+{
+public:
+ ARefBase() {}
+ virtual ~ARefBase() {}
+
+ NS_IMETHOD_ (MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_ (MozExternalRefCountType) Release() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp
new file mode 100644
index 000000000..d70c45110
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
+
+ArrayBufferInputStream::ArrayBufferInputStream()
+: mBufferLength(0)
+, mPos(0)
+, mClosed(false)
+{
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
+ uint32_t aByteOffset,
+ uint32_t aLength,
+ JSContext* aCx)
+{
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject());
+ if (!JS_IsArrayBufferObject(arrayBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
+ uint32_t offset = std::min(buflen, aByteOffset);
+ mBufferLength = std::min(buflen - offset, aLength);
+
+ mArrayBuffer = mozilla::MakeUnique<char[]>(mBufferLength);
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+ memcpy(&mArrayBuffer[0], src, mBufferLength);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount)
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t aCount, uint32_t *result)
+{
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = true;
+ return NS_OK;
+}
diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h
new file mode 100644
index 000000000..ddd3e7cef
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "js/Value.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID \
+{ /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \
+ 0x3014dde6, \
+ 0xaa1c, \
+ 0x41db, \
+ {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \
+}
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+public:
+ ArrayBufferInputStream();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+private:
+ virtual ~ArrayBufferInputStream() {}
+ mozilla::UniquePtr<char[]> mArrayBuffer;
+ uint32_t mBufferLength;
+ uint32_t mPos;
+ bool mClosed;
+};
+
+#endif // ArrayBufferInputStream_h
diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h
new file mode 100644
index 000000000..43ab27133
--- /dev/null
+++ b/netwerk/base/AutoClose.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AutoClose_h
+#define mozilla_net_AutoClose_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla { namespace net {
+
+// Like an nsAutoPtr for XPCOM streams (e.g. nsIAsyncInputStream) and other
+// refcounted classes that need to have the Close() method called explicitly
+// before they are destroyed.
+template <typename T>
+class AutoClose
+{
+public:
+ AutoClose() : mMutex("net::AutoClose.mMutex") { }
+ ~AutoClose(){
+ CloseAndRelease();
+ }
+
+ explicit operator bool()
+ {
+ MutexAutoLock lock(mMutex);
+ return mPtr;
+ }
+
+ already_AddRefed<T> forget()
+ {
+ MutexAutoLock lock(mMutex);
+ return mPtr.forget();
+ }
+
+ void takeOver(nsCOMPtr<T> & rhs)
+ {
+ already_AddRefed<T> other = rhs.forget();
+ TakeOverInternal(&other);
+ }
+
+ void CloseAndRelease()
+ {
+ TakeOverInternal(nullptr);
+ }
+
+private:
+ void TakeOverInternal(already_AddRefed<T> *aOther)
+ {
+ nsCOMPtr<T> ptr;
+ {
+ MutexAutoLock lock(mMutex);
+ ptr.swap(mPtr);
+ if (aOther) {
+ mPtr = *aOther;
+ }
+ }
+
+ if (ptr) {
+ ptr->Close();
+ }
+ }
+
+ void operator=(const AutoClose<T> &) = delete;
+ AutoClose(const AutoClose<T> &) = delete;
+
+ nsCOMPtr<T> mPtr;
+ Mutex mMutex;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AutoClose_h
diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp
new file mode 100644
index 000000000..e4bc05826
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -0,0 +1,1273 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundFileSaver.h"
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMArray.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsIPipe.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsIX509CertList.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "pk11pub.h"
+#include "secoidt.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <softpub.h>
+#include <wintrust.h>
+#endif // XP_WIN
+
+namespace mozilla {
+namespace net {
+
+// MOZ_LOG=BackgroundFileSaver:5
+static LazyLogModule prlog("BackgroundFileSaver");
+#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+/**
+ * Buffer size for writing to the output file or reading from the input file.
+ */
+#define BUFFERED_IO_SIZE (1024 * 32)
+
+/**
+ * When this upper limit is reached, the original request is suspended.
+ */
+#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
+
+/**
+ * When this lower limit is reached, the original request is resumed.
+ */
+#define REQUEST_RESUME_AT (1024 * 1024 * 2)
+
+////////////////////////////////////////////////////////////////////////////////
+//// NotifyTargetChangeRunnable
+
+/**
+ * Runnable object used to notify the control thread that file contents will now
+ * be saved to the specified file.
+ */
+class NotifyTargetChangeRunnable final : public Runnable
+{
+public:
+ NotifyTargetChangeRunnable(BackgroundFileSaver *aSaver, nsIFile *aTarget)
+ : mSaver(aSaver)
+ , mTarget(aTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return mSaver->NotifyTargetChange(mTarget);
+ }
+
+private:
+ RefPtr<BackgroundFileSaver> mSaver;
+ nsCOMPtr<nsIFile> mTarget;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+uint32_t BackgroundFileSaver::sThreadCount = 0;
+uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
+
+BackgroundFileSaver::BackgroundFileSaver()
+: mControlThread(nullptr)
+, mWorkerThread(nullptr)
+, mPipeOutputStream(nullptr)
+, mPipeInputStream(nullptr)
+, mObserver(nullptr)
+, mLock("BackgroundFileSaver.mLock")
+, mWorkerThreadAttentionRequested(false)
+, mFinishRequested(false)
+, mComplete(false)
+, mStatus(NS_OK)
+, mAppend(false)
+, mInitialTarget(nullptr)
+, mInitialTargetKeepPartial(false)
+, mRenamedTarget(nullptr)
+, mRenamedTargetKeepPartial(false)
+, mAsyncCopyContext(nullptr)
+, mSha256Enabled(false)
+, mSignatureInfoEnabled(false)
+, mActualTarget(nullptr)
+, mActualTargetKeepPartial(false)
+, mDigestContext(nullptr)
+{
+ LOG(("Created BackgroundFileSaver [this = %p]", this));
+}
+
+BackgroundFileSaver::~BackgroundFileSaver()
+{
+ LOG(("Destroying BackgroundFileSaver [this = %p]", this));
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+void
+BackgroundFileSaver::destructorSafeDestroyNSSReference()
+{
+ mDigestContext = nullptr;
+}
+
+void
+BackgroundFileSaver::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult rv;
+
+ rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream),
+ getter_AddRefs(mPipeOutputStream), true, true, 0,
+ HasInfiniteBuffer() ? UINT32_MAX : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetCurrentThread(getter_AddRefs(mControlThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewThread(getter_AddRefs(mWorkerThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sThreadCount++;
+ if (sThreadCount > sTelemetryMaxThreadCount) {
+ sTelemetryMaxThreadCount = sThreadCount;
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver **aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+ *aObserver = mObserver;
+ NS_IF_ADDREF(*aObserver);
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver *aObserver)
+{
+ mObserver = aObserver;
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::EnableAppend()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ MutexAutoLock lock(mLock);
+ mAppend = true;
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetTarget(nsIFile *aTarget, bool aKeepPartial)
+{
+ NS_ENSURE_ARG(aTarget);
+ {
+ MutexAutoLock lock(mLock);
+ if (!mInitialTarget) {
+ aTarget->Clone(getter_AddRefs(mInitialTarget));
+ mInitialTargetKeepPartial = aKeepPartial;
+ } else {
+ aTarget->Clone(getter_AddRefs(mRenamedTarget));
+ mRenamedTargetKeepPartial = aKeepPartial;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // rename or create the target file as requested, and start copying data.
+ return GetWorkerThreadAttention(true);
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::Finish(nsresult aStatus)
+{
+ nsresult rv;
+
+ // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
+ // all the data that is still in the pipe, and then finish.
+ rv = mPipeOutputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure that, when we get attention from the worker thread, if no pending
+ // rename operation is waiting, the operation will complete.
+ {
+ MutexAutoLock lock(mLock);
+ mFinishRequested = true;
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // process the completion conditions, detect that completion is requested, and
+ // notify the main thread of the completion. If this function was called with
+ // a success code, we wait for the copy to finish before processing the
+ // completion conditions, otherwise we interrupt the copy immediately.
+ return GetWorkerThreadAttention(NS_FAILED(aStatus));
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSha256()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable sha256 or initialize NSS off the main thread");
+ // Ensure Personal Security Manager is initialized. This is required for
+ // PK11_* operations to work.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSha256Enabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSha256Hash(nsACString& aHash)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
+ // We acquire a lock because mSha256 is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (mSha256.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aHash = mSha256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSignatureInfo()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable signature extraction off the main thread");
+ // Ensure Personal Security Manager is initialized.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSignatureInfoEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
+ // We acquire a lock because mSignatureInfo is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (!mComplete || !mSignatureInfoEnabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ for (int i = 0; i < mSignatureInfo.Count(); ++i) {
+ sigArray->AppendElement(mSignatureInfo[i], false);
+ }
+ *aSignatureInfo = sigArray;
+ NS_IF_ADDREF(*aSignatureInfo);
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy)
+{
+ nsresult rv;
+
+ MutexAutoLock lock(mLock);
+
+ // We only require attention one time. If this function is called two times
+ // before the worker thread wakes up, and the first has aShouldInterruptCopy
+ // false and the second true, we won't forcibly interrupt the copy from the
+ // control thread. However, that never happens, because calling Finish with a
+ // success code is the only case that may result in aShouldInterruptCopy being
+ // false. In that case, we won't call this function again, because consumers
+ // should not invoke other methods on the control thread after calling Finish.
+ // And in any case, Finish already closes one end of the pipe, causing the
+ // copy to finish properly on its own.
+ if (mWorkerThreadAttentionRequested) {
+ return NS_OK;
+ }
+
+ if (!mAsyncCopyContext) {
+ // Copy is not in progress, post an event to handle the change manually.
+ rv = mWorkerThread->Dispatch(NewRunnableMethod(this,
+ &BackgroundFileSaver::ProcessAttention),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aShouldInterruptCopy) {
+ // Interrupt the copy. The copy will be resumed, if needed, by the
+ // ProcessAttention function, invoked by the AsyncCopyCallback function.
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ }
+
+ // Indicate that attention has been requested successfully, there is no need
+ // to post another event until the worker thread processes the current one.
+ mWorkerThreadAttentionRequested = true;
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void
+BackgroundFileSaver::AsyncCopyCallback(void *aClosure, nsresult aStatus)
+{
+ BackgroundFileSaver *self = (BackgroundFileSaver *)aClosure;
+ {
+ MutexAutoLock lock(self->mLock);
+
+ // Now that the copy was interrupted or terminated, any notification from
+ // the control thread requires an event to be posted to the worker thread.
+ self->mAsyncCopyContext = nullptr;
+
+ // When detecting failures, ignore the status code we use to interrupt.
+ if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
+ NS_SUCCEEDED(self->mStatus)) {
+ self->mStatus = aStatus;
+ }
+ }
+
+ (void)self->ProcessAttention();
+
+ // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
+ // alive even if other references disappeared. At this point, we've finished
+ // using the object and can safely release our reference.
+ NS_RELEASE(self);
+}
+
+// Called on the worker thread.
+nsresult
+BackgroundFileSaver::ProcessAttention()
+{
+ nsresult rv;
+
+ // This function is called whenever the attention of the worker thread has
+ // been requested. This may happen in these cases:
+ // * We are about to start the copy for the first time. In this case, we are
+ // called from an event posted on the worker thread from the control thread
+ // by GetWorkerThreadAttention, and mAsyncCopyContext is null.
+ // * We have interrupted the copy for some reason. In this case, we are
+ // called by AsyncCopyCallback, and mAsyncCopyContext is null.
+ // * We are currently executing ProcessStateChange, and attention is requested
+ // by the control thread, for example because SetTarget or Finish have been
+ // called. In this case, we are called from from an event posted through
+ // GetWorkerThreadAttention. While mAsyncCopyContext was always null when
+ // the event was posted, at this point mAsyncCopyContext may not be null
+ // anymore, because ProcessStateChange may have started the copy before the
+ // event that called this function was processed on the worker thread.
+ // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
+ // through AsyncCopyCallback. This allows us to check if, for instance, we
+ // should rename the target file. We will then restart the copy if needed.
+ if (mAsyncCopyContext) {
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ return NS_OK;
+ }
+ // Use the current shared state to determine the next operation to execute.
+ rv = ProcessStateChange();
+ if (NS_FAILED(rv)) {
+ // If something failed while processing, terminate the operation now.
+ {
+ MutexAutoLock lock(mLock);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ }
+ // Ensure we notify completion now that the operation failed.
+ CheckCompletion();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+nsresult
+BackgroundFileSaver::ProcessStateChange()
+{
+ nsresult rv;
+
+ // We might have been notified because the operation is complete, verify.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Get a copy of the current shared state for the worker thread.
+ nsCOMPtr<nsIFile> initialTarget;
+ bool initialTargetKeepPartial;
+ nsCOMPtr<nsIFile> renamedTarget;
+ bool renamedTargetKeepPartial;
+ bool sha256Enabled;
+ bool append;
+ {
+ MutexAutoLock lock(mLock);
+
+ initialTarget = mInitialTarget;
+ initialTargetKeepPartial = mInitialTargetKeepPartial;
+ renamedTarget = mRenamedTarget;
+ renamedTargetKeepPartial = mRenamedTargetKeepPartial;
+ sha256Enabled = mSha256Enabled;
+ append = mAppend;
+
+ // From now on, another attention event needs to be posted if state changes.
+ mWorkerThreadAttentionRequested = false;
+ }
+
+ // The initial target can only be null if it has never been assigned. In this
+ // case, there is nothing to do since we never created any output file.
+ if (!initialTarget) {
+ return NS_OK;
+ }
+
+ // Determine if we are processing the attention request for the first time.
+ bool isContinuation = !!mActualTarget;
+ if (!isContinuation) {
+ // Assign the target file for the first time.
+ mActualTarget = initialTarget;
+ mActualTargetKeepPartial = initialTargetKeepPartial;
+ }
+
+ // Verify whether we have actually been instructed to use a different file.
+ // This may happen the first time this function is executed, if SetTarget was
+ // called two times before the worker thread processed the attention request.
+ bool equalToCurrent = false;
+ if (renamedTarget) {
+ rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!equalToCurrent)
+ {
+ // If we were asked to rename the file but the initial file did not exist,
+ // we simply create the file in the renamed location. We avoid this check
+ // if we have already started writing the output file ourselves.
+ bool exists = true;
+ if (!isContinuation) {
+ rv = mActualTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (exists) {
+ // We are moving the previous target file to a different location.
+ nsCOMPtr<nsIFile> renamedTargetParentDir;
+ rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString renamedTargetName;
+ rv = renamedTarget->GetLeafName(renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We must delete any existing target file before moving the current
+ // one.
+ rv = renamedTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = renamedTarget->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Move the file. If this fails, we still reference the original file
+ // in mActualTarget, so that it is deleted if requested. If this
+ // succeeds, the nsIFile instance referenced by mActualTarget mutates
+ // and starts pointing to the new file, but we'll discard the reference.
+ rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Now we can update the actual target file name.
+ mActualTarget = renamedTarget;
+ mActualTargetKeepPartial = renamedTargetKeepPartial;
+ }
+ }
+
+ // Notify if the target file name actually changed.
+ if (!equalToCurrent) {
+ // We must clone the nsIFile instance because mActualTarget is not
+ // immutable, it may change if the target is renamed later.
+ nsCOMPtr<nsIFile> actualTargetToNotify;
+ rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<NotifyTargetChangeRunnable> event =
+ new NotifyTargetChangeRunnable(this, actualTargetToNotify);
+ NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+ rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isContinuation) {
+ // The pending rename operation might be the last task before finishing. We
+ // may return here only if we have already created the target file.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Even if the operation did not complete, the pipe input stream may be
+ // empty and may have been closed already. We detect this case using the
+ // Available property, because it never returns an error if there is more
+ // data to be consumed. If the pipe input stream is closed, we just exit
+ // and wait for more calls like SetTarget or Finish to be invoked on the
+ // control thread. However, we still truncate the file or create the
+ // initial digest context if we are expected to do that.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // Create the digest context if requested and NSS hasn't been shut down.
+ if (sha256Enabled && !mDigestContext) {
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ mDigestContext = UniquePK11Context(
+ PK11_CreateDigestContext(SEC_OID_SHA256));
+ NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ // When we are requested to append to an existing file, we should read the
+ // existing data and ensure we include it as part of the final hash.
+ if (mDigestContext && append && !isContinuation) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mActualTarget,
+ PR_RDONLY | nsIFile::OS_READAHEAD);
+ if (rv != NS_ERROR_FILE_NOT_FOUND) {
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[BUFFERED_IO_SIZE];
+ while (true) {
+ uint32_t count;
+ rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0) {
+ // We reached the end of the file.
+ break;
+ }
+
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = MapSECStatus(
+ PK11_DigestOp(mDigestContext.get(),
+ BitwiseCast<unsigned char*, char*>(buffer),
+ count));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // We will append to the initial target file only if it was requested by the
+ // caller, but we'll always append on subsequent accesses to the target file.
+ int32_t creationIoFlags;
+ if (isContinuation) {
+ creationIoFlags = PR_APPEND;
+ } else {
+ creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
+ }
+
+ // Create the target file, or append to it if we already started writing it.
+ // The 0600 permissions are used while the file is being downloaded, and for
+ // interrupted downloads. Those may be located in the system temporary
+ // directory, as well as the target directory, and generally have a ".part"
+ // extension. Those part files should never be group or world-writable even
+ // if the umask allows it.
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
+ mActualTarget,
+ PR_WRONLY | creationIoFlags, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE);
+ if (!outputStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Wrap the output stream so that it feeds the digest context if needed.
+ if (mDigestContext) {
+ // No need to acquire the NSS lock here, DigestOutputStream must acquire it
+ // in any case before each asynchronous write. Constructing the
+ // DigestOutputStream cannot fail. Passing mDigestContext to
+ // DigestOutputStream is safe, because BackgroundFileSaver always outlives
+ // the outputStream. BackgroundFileSaver is reference-counted before the
+ // call to AsyncCopy, and mDigestContext is never destroyed before
+ // AsyncCopyCallback.
+ outputStream = new DigestOutputStream(outputStream, mDigestContext.get());
+ }
+
+ // Start copying our input to the target file. No errors can be raised past
+ // this point if the copy starts, since they should be handled by the thread.
+ {
+ MutexAutoLock lock(mLock);
+
+ rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
+ this, false, true, getter_AddRefs(mAsyncCopyContext),
+ GetProgressCallback());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("NS_AsyncCopy failed.");
+ mAsyncCopyContext = nullptr;
+ return rv;
+ }
+ }
+
+ // If the operation succeeded, we must ensure that we keep this object alive
+ // for the entire duration of the copy, since only the raw pointer will be
+ // provided as the argument of the AsyncCopyCallback function. We can add the
+ // reference now, after NS_AsyncCopy returned, because it always starts
+ // processing asynchronously, and there is no risk that the callback is
+ // invoked before we reach this point. If the operation failed instead, then
+ // AsyncCopyCallback will never be called.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+bool
+BackgroundFileSaver::CheckCompletion()
+{
+ nsresult rv;
+
+ MOZ_ASSERT(!mAsyncCopyContext,
+ "Should not be copying when checking completion conditions.");
+
+ bool failed = true;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mComplete) {
+ return true;
+ }
+
+ // If an error occurred, we don't need to do the checks in this code block,
+ // and the operation can be completed immediately with a failure code.
+ if (NS_SUCCEEDED(mStatus)) {
+ failed = false;
+
+ // We did not incur in an error, so we must determine if we can stop now.
+ // If the Finish method has not been called, we can just continue now.
+ if (!mFinishRequested) {
+ return false;
+ }
+
+ // We can only stop when all the operations requested by the control
+ // thread have been processed. First, we check whether we have processed
+ // the first SetTarget call, if any. Then, we check whether we have
+ // processed any rename requested by subsequent SetTarget calls.
+ if ((mInitialTarget && !mActualTarget) ||
+ (mRenamedTarget && mRenamedTarget != mActualTarget)) {
+ return false;
+ }
+
+ // If we still have data to write to the output file, allow the copy
+ // operation to resume. The Available getter may return an error if one
+ // of the pipe's streams has been already closed.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available != 0) {
+ return false;
+ }
+ }
+
+ mComplete = true;
+ }
+
+ // Ensure we notify completion now that the operation finished.
+ // Do a best-effort attempt to remove the file if required.
+ if (failed && mActualTarget && !mActualTargetKeepPartial) {
+ (void)mActualTarget->Remove(false);
+ }
+
+ // Finish computing the hash
+ if (!failed && mDigestContext) {
+ nsNSSShutDownPreventionLock lock;
+ if (!isAlreadyShutDown()) {
+ Digest d;
+ rv = d.End(SEC_OID_SHA256, mDigestContext);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mLock);
+ mSha256 =
+ nsDependentCSubstring(BitwiseCast<char*, unsigned char*>(d.get().data),
+ d.get().len);
+ }
+ }
+ }
+
+ // Compute the signature of the binary. ExtractSignatureInfo doesn't do
+ // anything on non-Windows platforms except return an empty nsIArray.
+ if (!failed && mActualTarget) {
+ nsString filePath;
+ mActualTarget->GetTarget(filePath);
+ nsresult rv = ExtractSignatureInfo(filePath);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to extract signature information [this = %p].", this));
+ } else {
+ LOG(("Signature extraction success! [this = %p]", this));
+ }
+ }
+
+ // Post an event to notify that the operation completed.
+ if (NS_FAILED(mControlThread->Dispatch(NewRunnableMethod(this,
+ &BackgroundFileSaver::NotifySaveComplete),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post completion event to the control thread.");
+ }
+
+ return true;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::NotifyTargetChange(nsIFile *aTarget)
+{
+ if (mObserver) {
+ (void)mObserver->OnTargetChange(this, aTarget);
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaver::NotifySaveComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult status;
+ {
+ MutexAutoLock lock(mLock);
+ status = mStatus;
+ }
+
+ if (mObserver) {
+ (void)mObserver->OnSaveComplete(this, status);
+ }
+
+ // At this point, the worker thread will not process any more events, and we
+ // can shut it down. Shutting down a thread may re-enter the event loop on
+ // this thread. This is not a problem in this case, since this function is
+ // called by a top-level event itself, and we have already invoked the
+ // completion observer callback. Re-entering the loop can only delay the
+ // final release and destruction of this saver object, since we are keeping a
+ // reference to it through the event object.
+ mWorkerThread->Shutdown();
+
+ sThreadCount--;
+
+ // When there are no more active downloads, we consider the download session
+ // finished. We record the maximum number of concurrent downloads reached
+ // during the session in a telemetry histogram, and we reset the maximum
+ // thread counter for the next download session
+ if (sThreadCount == 0) {
+ Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,
+ sTelemetryMaxThreadCount);
+ sTelemetryMaxThreadCount = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
+
+ nsNSSShutDownPreventionLock nssLock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ if (!mSignatureInfoEnabled) {
+ return NS_OK;
+ }
+ }
+ nsresult rv;
+ nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_WIN
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck = {0};
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath.Data();
+ fileToCheck.hFile = nullptr;
+ fileToCheck.pgKnownSubject = nullptr;
+
+ // We want to check it is signed and trusted.
+ WINTRUST_DATA trustData = {0};
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // Disallow revocation checks over the network
+ trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
+ // chains up to a trusted root CA and has appropriate permissions to sign
+ // code.
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted. If the file is
+ // not signed, this is a no-op.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
+ // According to the Windows documentation, we should check against 0 instead
+ // of ERROR_SUCCESS, which is an HRESULT.
+ if (ret == 0) {
+ cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
+ }
+ if (cryptoProviderData) {
+ // Lock because signature information is read on the main thread.
+ MutexAutoLock lock(mLock);
+ LOG(("Downloaded trusted and signed file [this = %p].", this));
+ // A binary may have multiple signers. Each signer may have multiple certs
+ // in the chain.
+ for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
+ const CERT_CHAIN_CONTEXT* certChainContext =
+ cryptoProviderData->pasSigners[i].pChainContext;
+ if (!certChainContext) {
+ break;
+ }
+ for (DWORD j = 0; j < certChainContext->cChain; ++j) {
+ const CERT_SIMPLE_CHAIN* certSimpleChain =
+ certChainContext->rgpChain[j];
+ if (!certSimpleChain) {
+ break;
+ }
+ nsCOMPtr<nsIX509CertList> nssCertList =
+ do_CreateInstance(NS_X509CERTLIST_CONTRACTID);
+ if (!nssCertList) {
+ break;
+ }
+ bool extractionSuccess = true;
+ for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
+ CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
+ if (certChainElement->pCertContext->dwCertEncodingType !=
+ X509_ASN_ENCODING) {
+ continue;
+ }
+ nsCOMPtr<nsIX509Cert> nssCert = nullptr;
+ rv = certDB->ConstructX509(
+ reinterpret_cast<char *>(
+ certChainElement->pCertContext->pbCertEncoded),
+ certChainElement->pCertContext->cbCertEncoded,
+ getter_AddRefs(nssCert));
+ if (!nssCert) {
+ extractionSuccess = false;
+ LOG(("Couldn't create NSS cert [this = %p]", this));
+ break;
+ }
+ nssCertList->AddCert(nssCert);
+ nsString subjectName;
+ nssCert->GetSubjectName(subjectName);
+ LOG(("Adding cert %s [this = %p]",
+ NS_ConvertUTF16toUTF8(subjectName).get(), this));
+ }
+ if (extractionSuccess) {
+ mSignatureInfo.AppendObject(nssCertList);
+ }
+ }
+ }
+ // Free the provider data if cryptoProviderData is not null.
+ trustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ } else {
+ LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
+ }
+#endif
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream,
+ nsIBackgroundFileSaver,
+ nsIOutputStream,
+ nsIAsyncOutputStream,
+ nsIOutputStreamCallback)
+
+BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
+: BackgroundFileSaver()
+, mAsyncWaitCallback(nullptr)
+{
+}
+
+BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream()
+{
+}
+
+bool
+BackgroundFileSaverOutputStream::HasInfiniteBuffer()
+{
+ return false;
+}
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverOutputStream::GetProgressCallback()
+{
+ return nullptr;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Close()
+{
+ return mPipeOutputStream->Close();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Flush()
+{
+ return mPipeOutputStream->Flush();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Write(const char *aBuf, uint32_t aCount,
+ uint32_t *_retval)
+{
+ return mPipeOutputStream->Write(aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream *aFromStream,
+ uint32_t aCount, uint32_t *_retval)
+{
+ return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void *aClosure, uint32_t aCount,
+ uint32_t *_retval)
+{
+ return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::IsNonBlocking(bool *_retval)
+{
+ return mPipeOutputStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason)
+{
+ return mPipeOutputStream->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ NS_ENSURE_STATE(!mAsyncWaitCallback);
+
+ mAsyncWaitCallback = aCallback;
+
+ return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::OnOutputStreamReady(
+ nsIAsyncOutputStream *aStream)
+{
+ NS_ENSURE_STATE(mAsyncWaitCallback);
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
+ asyncWaitCallback.swap(mAsyncWaitCallback);
+
+ return asyncWaitCallback->OnOutputStreamReady(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener,
+ nsIBackgroundFileSaver,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener()
+: BackgroundFileSaver()
+, mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock")
+, mReceivedTooMuchData(false)
+, mRequest(nullptr)
+, mRequestSuspended(false)
+{
+}
+
+BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener()
+{
+}
+
+bool
+BackgroundFileSaverStreamListener::HasInfiniteBuffer()
+{
+ return true;
+}
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverStreamListener::GetProgressCallback()
+{
+ return AsyncCopyProgressCallback;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ NS_ENSURE_ARG(aRequest);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ // If an error occurred, cancel the operation immediately. On success, wait
+ // until the caller has determined whether the file should be renamed.
+ if (NS_FAILED(aStatusCode)) {
+ Finish(aStatusCode);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG(aRequest);
+
+ // Read the requested data. Since the pipe has an infinite buffer, we don't
+ // expect any write error to occur here.
+ uint32_t writeCount;
+ rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If reading from the input stream fails for any reason, the pipe will return
+ // a success code, but without reading all the data. Since we should be able
+ // to read the requested data when OnDataAvailable is called, raise an error.
+ if (writeCount < aCount) {
+ NS_WARNING("Reading from the input stream should not have failed.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stateChanged = false;
+ {
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (!mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
+ mReceivedTooMuchData = true;
+ mRequest = aRequest;
+ stateChanged = true;
+ }
+ }
+ }
+
+ if (stateChanged) {
+ NotifySuspendOrResume();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void
+BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void *aClosure,
+ uint32_t aCount)
+{
+ BackgroundFileSaverStreamListener *self =
+ (BackgroundFileSaverStreamListener *)aClosure;
+
+ // Wait if the control thread is in the process of suspending or resuming.
+ MutexAutoLock lock(self->mSuspensionLock);
+
+ // This function is called when some bytes are consumed by NS_AsyncCopy. Each
+ // time this happens, verify if a suspended request should be resumed, because
+ // we have now consumed enough data.
+ if (self->mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = self->mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
+ self->mReceivedTooMuchData = false;
+
+ // Post an event to verify if the request should be resumed.
+ if (NS_FAILED(self->mControlThread->Dispatch(NewRunnableMethod(self,
+ &BackgroundFileSaverStreamListener::NotifySuspendOrResume),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post resume event to the control thread.");
+ }
+ }
+ }
+}
+
+// Called on the control thread.
+nsresult
+BackgroundFileSaverStreamListener::NotifySuspendOrResume()
+{
+ // Prevent the worker thread from changing state while processing.
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (mReceivedTooMuchData) {
+ if (!mRequestSuspended) {
+ // Try to suspend the request. If this fails, don't try to resume later.
+ if (NS_SUCCEEDED(mRequest->Suspend())) {
+ mRequestSuspended = true;
+ } else {
+ NS_WARNING("Unable to suspend the request.");
+ }
+ }
+ } else {
+ if (mRequestSuspended) {
+ // Resume the request only if we succeeded in suspending it.
+ if (NS_SUCCEEDED(mRequest->Resume())) {
+ mRequestSuspended = false;
+ } else {
+ NS_WARNING("Unable to resume the request.");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// DigestOutputStream
+NS_IMPL_ISUPPORTS(DigestOutputStream,
+ nsIOutputStream)
+
+DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
+ PK11Context* aContext) :
+ mOutputStream(aStream)
+ , mDigestContext(aContext)
+{
+ MOZ_ASSERT(mDigestContext, "Can't have null digest context");
+ MOZ_ASSERT(mOutputStream, "Can't have null output stream");
+}
+
+DigestOutputStream::~DigestOutputStream()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Close()
+{
+ return mOutputStream->Close();
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Flush()
+{
+ return mOutputStream->Flush();
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval)
+{
+ nsNSSShutDownPreventionLock lock;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = MapSECStatus(
+ PK11_DigestOp(mDigestContext,
+ BitwiseCast<const unsigned char*, const char*>(aBuf),
+ aCount));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOutputStream->Write(aBuf, aCount, retval);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* retval)
+{
+ // Not supported. We could read the stream to a buf, call DigestOp on the
+ // result, seek back and pass the stream on, but it's not worth it since our
+ // application (NS_AsyncCopy) doesn't invoke this on the sink.
+ MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void *aClosure, uint32_t aCount,
+ uint32_t *retval)
+{
+ MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::IsNonBlocking(bool *retval)
+{
+ return mOutputStream->IsNonBlocking(retval);
+}
+
+#undef LOG_ENABLED
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h
new file mode 100644
index 000000000..1fa9268f8
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.h
@@ -0,0 +1,418 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file defines two implementations of the nsIBackgroundFileSaver
+ * interface. See the "test_backgroundfilesaver.js" file for usage examples.
+ */
+
+#ifndef BackgroundFileSaver_h__
+#define BackgroundFileSaver_h__
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBackgroundFileSaver.h"
+#include "nsIStreamListener.h"
+#include "nsNSSShutDown.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+
+class nsIAsyncInputStream;
+class nsIThread;
+class nsIX509CertList;
+
+namespace mozilla {
+namespace net {
+
+class DigestOutputStream;
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+class BackgroundFileSaver : public nsIBackgroundFileSaver,
+ public nsNSSShutDownObject
+{
+public:
+ NS_DECL_NSIBACKGROUNDFILESAVER
+
+ BackgroundFileSaver();
+
+ /**
+ * Initializes the pipe and the worker thread on XPCOM construction.
+ *
+ * This is called automatically by the XPCOM infrastructure, and if this
+ * fails, the factory will delete this object without returning a reference.
+ */
+ nsresult Init();
+
+ /**
+ * Used by nsNSSShutDownList to manage nsNSSShutDownObjects.
+ */
+ void virtualDestroyNSSReference() override;
+
+ /**
+ * Number of worker threads that are currently running.
+ */
+ static uint32_t sThreadCount;
+
+ /**
+ * Maximum number of worker threads reached during the current download session,
+ * used for telemetry.
+ *
+ * When there are no more worker threads running, we consider the download
+ * session finished, and this counter is reset.
+ */
+ static uint32_t sTelemetryMaxThreadCount;
+
+
+protected:
+ virtual ~BackgroundFileSaver();
+
+ /**
+ * Helper function for managing NSS objects (mDigestContext).
+ */
+ void destructorSafeDestroyNSSReference();
+
+ /**
+ * Thread that constructed this object.
+ */
+ nsCOMPtr<nsIThread> mControlThread;
+
+ /**
+ * Thread to which the actual input/output is delegated.
+ */
+ nsCOMPtr<nsIThread> mWorkerThread;
+
+ /**
+ * Stream that receives data from derived classes. The received data will be
+ * available to the worker thread through mPipeInputStream. This is an
+ * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream.
+ */
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream;
+
+ /**
+ * Used during initialization, determines if the pipe is created with an
+ * infinite buffer. An infinite buffer is required if the derived class
+ * implements nsIStreamListener, because this interface requires all the
+ * provided data to be consumed synchronously.
+ */
+ virtual bool HasInfiniteBuffer() = 0;
+
+ /**
+ * Used by derived classes if they need to be called back while copying.
+ */
+ virtual nsAsyncCopyProgressFun GetProgressCallback() = 0;
+
+ /**
+ * Stream used by the worker thread to read the data to be saved.
+ */
+ nsCOMPtr<nsIAsyncInputStream> mPipeInputStream;
+
+private:
+ friend class NotifyTargetChangeRunnable;
+
+ /**
+ * Matches the nsIBackgroundFileSaver::observer property.
+ *
+ * @remarks This is a strong reference so that JavaScript callers don't need
+ * to worry about keeping another reference to the observer.
+ */
+ nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared state between control and worker threads
+
+ /**
+ * Protects the shared state between control and worker threads. This mutex
+ * is always locked for a very short time, never during input/output.
+ */
+ mozilla::Mutex mLock;
+
+ /**
+ * True if the worker thread is already waiting to process a change in state.
+ */
+ bool mWorkerThreadAttentionRequested;
+
+ /**
+ * True if the operation should finish as soon as possibile.
+ */
+ bool mFinishRequested;
+
+ /**
+ * True if the operation completed, with either success or failure.
+ */
+ bool mComplete;
+
+ /**
+ * Holds the current file saver status. This is a success status while the
+ * object is working correctly, and remains such if the operation completes
+ * successfully. This becomes an error status when an error occurs on the
+ * worker thread, or when the operation is canceled.
+ */
+ nsresult mStatus;
+
+ /**
+ * True if we should append data to the initial target file, instead of
+ * overwriting it.
+ */
+ bool mAppend;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and contains
+ * the target file name that will be used by the worker thread, as soon as it
+ * is possible to update mActualTarget and open the file. This is null if no
+ * target was ever assigned to this object.
+ */
+ nsCOMPtr<nsIFile> mInitialTarget;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and
+ * indicates whether mInitialTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mInitialTargetKeepPartial;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * contains the new target file name to which the worker thread will move the
+ * target file, as soon as it can be done. This is null if SetTarget was
+ * called only once, or no target was ever assigned to this object.
+ *
+ * The target file can be renamed multiple times, though only the most recent
+ * rename is guaranteed to be processed by the worker thread.
+ */
+ nsCOMPtr<nsIFile> mRenamedTarget;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * indicates whether mRenamedTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mRenamedTargetKeepPartial;
+
+ /**
+ * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise.
+ * This is read by both threads but only written by the worker thread.
+ */
+ nsCOMPtr<nsISupports> mAsyncCopyContext;
+
+ /**
+ * The SHA 256 hash in raw bytes of the downloaded file. This is written
+ * by the worker thread but can be read on the main thread.
+ */
+ nsCString mSha256;
+
+ /**
+ * Whether or not to compute the hash. Must be set on the main thread before
+ * setTarget is called.
+ */
+ bool mSha256Enabled;
+
+ /**
+ * Store the signature info.
+ */
+ nsCOMArray<nsIX509CertList> mSignatureInfo;
+
+ /**
+ * Whether or not to extract the signature. Must be set on the main thread
+ * before setTarget is called.
+ */
+ bool mSignatureInfoEnabled;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// State handled exclusively by the worker thread
+
+ /**
+ * Current target file associated to the input and output streams.
+ */
+ nsCOMPtr<nsIFile> mActualTarget;
+
+ /**
+ * Indicates whether mActualTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mActualTargetKeepPartial;
+
+ /**
+ * Used to calculate the file hash. This keeps state across file renames and
+ * is lazily initialized in ProcessStateChange.
+ */
+ UniquePK11Context mDigestContext;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Private methods
+
+ /**
+ * Called when NS_AsyncCopy completes.
+ *
+ * @param aClosure
+ * Populated with a raw pointer to the BackgroundFileSaver object.
+ * @param aStatus
+ * Success or failure status specified when the copy was interrupted.
+ */
+ static void AsyncCopyCallback(void *aClosure, nsresult aStatus);
+
+ /**
+ * Called on the control thread after state changes, to ensure that the worker
+ * thread will process the state change appropriately.
+ *
+ * @param aShouldInterruptCopy
+ * If true, the current NS_AsyncCopy, if any, is canceled.
+ */
+ nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy);
+
+ /**
+ * Event called on the worker thread to begin processing a state change.
+ */
+ nsresult ProcessAttention();
+
+ /**
+ * Called by ProcessAttention to execute the operations corresponding to the
+ * state change. If this results in an error, ProcessAttention will force the
+ * entire operation to be aborted.
+ */
+ nsresult ProcessStateChange();
+
+ /**
+ * Returns true if completion conditions are met on the worker thread. The
+ * first time this happens, posts the completion event to the control thread.
+ */
+ bool CheckCompletion();
+
+ /**
+ * Event called on the control thread to indicate that file contents will now
+ * be saved to the specified file.
+ */
+ nsresult NotifyTargetChange(nsIFile *aTarget);
+
+ /**
+ * Event called on the control thread to send the final notification.
+ */
+ nsresult NotifySaveComplete();
+
+ /**
+ * Verifies the signature of the binary at the specified file path and stores
+ * the signature data in mSignatureInfo. We extract only X.509 certificates,
+ * since that is what Google's Safebrowsing protocol specifies.
+ */
+ nsresult ExtractSignatureInfo(const nsAString& filePath);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+class BackgroundFileSaverOutputStream : public BackgroundFileSaver
+ , public nsIAsyncOutputStream
+ , public nsIOutputStreamCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ BackgroundFileSaverOutputStream();
+
+protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+private:
+ ~BackgroundFileSaverOutputStream();
+
+ /**
+ * Original callback provided to our AsyncWait wrapper.
+ */
+ nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener. This class is instantiated by
+// nsExternalHelperAppService, DownloadCore.jsm, and possibly others.
+
+class BackgroundFileSaverStreamListener final : public BackgroundFileSaver
+ , public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ BackgroundFileSaverStreamListener();
+
+protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+private:
+ ~BackgroundFileSaverStreamListener();
+
+ /**
+ * Protects the state related to whether the request should be suspended.
+ */
+ mozilla::Mutex mSuspensionLock;
+
+ /**
+ * Whether we should suspend the request because we received too much data.
+ */
+ bool mReceivedTooMuchData;
+
+ /**
+ * Request for which we received too much data. This is populated when
+ * mReceivedTooMuchData becomes true for the first time.
+ */
+ nsCOMPtr<nsIRequest> mRequest;
+
+ /**
+ * Whether mRequest is currently suspended.
+ */
+ bool mRequestSuspended;
+
+ /**
+ * Called while NS_AsyncCopy is copying data.
+ */
+ static void AsyncCopyProgressCallback(void *aClosure, uint32_t aCount);
+
+ /**
+ * Called on the control thread to suspend or resume the request.
+ */
+ nsresult NotifySuspendOrResume();
+};
+
+// A wrapper around nsIOutputStream, so that we can compute hashes on the
+// stream without copying and without polluting pristine NSS code with XPCOM
+// interfaces.
+class DigestOutputStream : public nsNSSShutDownObject,
+ public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ // Constructor. Neither parameter may be null. The caller owns both.
+ DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext);
+
+ // We don't own any NSS objects here, so no need to clean up
+ void virtualDestroyNSSReference() override { }
+
+private:
+ ~DigestOutputStream();
+
+ // Calls to write are passed to this stream.
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // Digest context used to compute the hash, owned by the caller.
+ PK11Context* mDigestContext;
+
+ // Don't accidentally copy construct.
+ DigestOutputStream(const DigestOutputStream& d);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp
new file mode 100644
index 000000000..f97fb41d7
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+
+static const char16_t kInterfaceName[] = u"captive-portal-inteface";
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort";
+static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success";
+
+static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gCaptivePortalLog("CaptivePortalService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
+ nsISupportsWeakReference, nsITimerCallback,
+ nsICaptivePortalCallback)
+
+CaptivePortalService::CaptivePortalService()
+ : mState(UNKNOWN)
+ , mStarted(false)
+ , mInitialized(false)
+ , mRequestInProgress(false)
+ , mEverBeenCaptive(false)
+ , mDelay(kDefaultInterval)
+ , mSlackCount(0)
+ , mMinInterval(kDefaultInterval)
+ , mMaxInterval(25*kDefaultInterval)
+ , mBackoffFactor(5.0)
+{
+ mLastChecked = TimeStamp::Now();
+}
+
+CaptivePortalService::~CaptivePortalService()
+{
+ LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
+ XRE_GetProcessType() == GeckoProcessType_Default));
+}
+
+nsresult
+CaptivePortalService::PerformCheck()
+{
+ LOG(("CaptivePortalService::PerformCheck mRequestInProgress:%d mInitialized:%d mStarted:%d\n",
+ mRequestInProgress, mInitialized, mStarted));
+ // Don't issue another request if last one didn't complete
+ if (mRequestInProgress || !mInitialized || !mStarted) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ nsresult rv;
+ if (!mCaptivePortalDetector) {
+ mCaptivePortalDetector =
+ do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to get a captive portal detector\n"));
+ return rv;
+ }
+ }
+
+ LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
+ mRequestInProgress = true;
+ mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::RearmTimer()
+{
+ LOG(("CaptivePortalService::RearmTimer\n"));
+ // Start a timer to recheck
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ // If we have successfully determined the state, and we have never detected
+ // a captive portal, we don't need to keep polling, but will rely on events
+ // to trigger detection.
+ if (mState == NOT_CAPTIVE) {
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ if (mTimer && mDelay > 0) {
+ LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
+ return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Initialize()
+{
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ // Only the main process service should actually do anything. The service in
+ // the content process only mirrors the CP state in the main process.
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
+ }
+
+ LOG(("Initialized CaptivePortalService\n"));
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Start()
+{
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ if (mStarted) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
+ mStarted = true;
+ mEverBeenCaptive = false;
+
+ // Get the delay prefs
+ Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval);
+ Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval);
+ Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor);
+
+ LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n",
+ mMinInterval, mMaxInterval, mBackoffFactor));
+
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ // When Start is called, perform a check immediately
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+nsresult
+CaptivePortalService::Stop()
+{
+ LOG(("CaptivePortalService::Stop\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything when called in the content process.
+ return NS_OK;
+ }
+
+ if (!mStarted) {
+ return NS_OK;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mRequestInProgress = false;
+ mStarted = false;
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->Abort(kInterfaceName);
+ }
+ mCaptivePortalDetector = nullptr;
+
+ // Clear the state in case anyone queries the state while detection is off.
+ mState = UNKNOWN;
+ return NS_OK;
+}
+
+void
+CaptivePortalService::SetStateInChild(int32_t aState)
+{
+ // This should only be called in the content process, from ContentChild.cpp
+ // in order to mirror the captive portal state set in the chrome process.
+ MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+
+ mState = aState;
+ mLastChecked = TimeStamp::Now();
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetState(int32_t *aState)
+{
+ *aState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::RecheckCaptivePortal()
+{
+ LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ // This is called for user activity. We need to reset the slack count,
+ // so the checks continue to be quite frequent.
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::GetLastChecked(uint64_t *aLastChecked)
+{
+ double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
+ *aLastChecked = static_cast<uint64_t>(duration);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsITimer
+// This callback gets called every mDelay miliseconds
+// It issues a checkCaptivePortal operation if one isn't already in progress
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Notify(nsITimer *aTimer)
+{
+ LOG(("CaptivePortalService::Notify\n"));
+ MOZ_ASSERT(aTimer == mTimer);
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ PerformCheck();
+
+ // This is needed because we don't want to always make requests very often.
+ // Every 10 checks, we the delay is increased mBackoffFactor times
+ // to a maximum delay of mMaxInterval
+ mSlackCount++;
+ if (mSlackCount % 10 == 0) {
+ mDelay = mDelay * mBackoffFactor;
+ }
+ if (mDelay > mMaxInterval) {
+ mDelay = mMaxInterval;
+ }
+
+ // Note - if mDelay is 0, the timer will not be rearmed.
+ RearmTimer();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsIObserver
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Observe(nsISupports *aSubject,
+ const char * aTopic,
+ const char16_t * aData)
+{
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // A redirect or altered content has been detected.
+ // The user needs to log in. We are in a captive portal.
+ mState = LOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mEverBeenCaptive = true;
+ } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
+ // The user has successfully logged in. We have connectivity.
+ mState = UNLOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ RearmTimer();
+ } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
+ // The login has been aborted
+ mState = UNKNOWN;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ }
+
+ // Send notification so that the captive portal state is mirrored in the
+ // content process.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, nullptr);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalCallback
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Prepare()
+{
+ LOG(("CaptivePortalService::Prepare\n"));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ // XXX: Finish preparation shouldn't be called until dns and routing is available.
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->FinishPreparation(kInterfaceName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::Complete(bool success)
+{
+ LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, mState));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ mLastChecked = TimeStamp::Now();
+
+ // Note: this callback gets called when:
+ // 1. the request is completed, and content is valid (success == true)
+ // 2. when the request is aborted or times out (success == false)
+
+ if (success) {
+ if (mEverBeenCaptive) {
+ mState = UNLOCKED_PORTAL;
+ } else {
+ mState = NOT_CAPTIVE;
+ }
+ }
+
+ mRequestInProgress = false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h
new file mode 100644
index 000000000..dd15450dc
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.h
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CaptivePortalService_h_
+#define CaptivePortalService_h_
+
+#include "nsICaptivePortalService.h"
+#include "nsICaptivePortalDetector.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace net {
+
+class CaptivePortalService
+ : public nsICaptivePortalService
+ , public nsIObserver
+ , public nsSupportsWeakReference
+ , public nsITimerCallback
+ , public nsICaptivePortalCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICAPTIVEPORTALSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICAPTIVEPORTALCALLBACK
+
+ CaptivePortalService();
+ nsresult Initialize();
+ nsresult Start();
+ nsresult Stop();
+
+ // This method is only called in the content process, in order to mirror
+ // the captive portal state in the parent process.
+ void SetStateInChild(int32_t aState);
+private:
+ virtual ~CaptivePortalService();
+ nsresult PerformCheck();
+ nsresult RearmTimer();
+
+ nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector;
+ int32_t mState;
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mStarted;
+ bool mInitialized;
+ bool mRequestInProgress;
+ bool mEverBeenCaptive;
+
+ uint32_t mDelay;
+ int32_t mSlackCount;
+
+ uint32_t mMinInterval;
+ uint32_t mMaxInterval;
+ float mBackoffFactor;
+
+ // This holds a timestamp when the last time when the captive portal check
+ // has changed state.
+ mozilla::TimeStamp mLastChecked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // CaptivePortalService_h_
diff --git a/netwerk/base/ChannelDiverterChild.cpp b/netwerk/base/ChannelDiverterChild.cpp
new file mode 100644
index 000000000..d275783f8
--- /dev/null
+++ b/netwerk/base/ChannelDiverterChild.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/PFTPChannelChild.h"
+#include "nsIDivertableChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterChild::ChannelDiverterChild()
+{
+}
+
+ChannelDiverterChild::~ChannelDiverterChild()
+{
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ChannelDiverterChild.h b/netwerk/base/ChannelDiverterChild.h
new file mode 100644
index 000000000..a92de2f11
--- /dev/null
+++ b/netwerk/base/ChannelDiverterChild.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _channeldiverterchild_h_
+#define _channeldiverterchild_h_
+
+#include "mozilla/net/PChannelDiverterChild.h"
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterChild :
+ public PChannelDiverterChild
+{
+public:
+ ChannelDiverterChild();
+ virtual ~ChannelDiverterChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterchild_h_ */
diff --git a/netwerk/base/ChannelDiverterParent.cpp b/netwerk/base/ChannelDiverterParent.cpp
new file mode 100644
index 000000000..3b66644ab
--- /dev/null
+++ b/netwerk/base/ChannelDiverterParent.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/FTPChannelParent.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/PFTPChannelParent.h"
+#include "ADivertableParentChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterParent::ChannelDiverterParent()
+{
+}
+
+ChannelDiverterParent::~ChannelDiverterParent()
+{
+}
+
+bool
+ChannelDiverterParent::Init(const ChannelDiverterArgs& aArgs)
+{
+ switch (aArgs.type()) {
+ case ChannelDiverterArgs::THttpChannelDiverterArgs:
+ {
+ auto httpParent = static_cast<HttpChannelParent*>(
+ aArgs.get_HttpChannelDiverterArgs().mChannelParent());
+ httpParent->SetApplyConversion(aArgs.get_HttpChannelDiverterArgs().mApplyConversion());
+
+ mDivertableChannelParent =
+ static_cast<ADivertableParentChannel*>(httpParent);
+ break;
+ }
+ case ChannelDiverterArgs::TPFTPChannelParent:
+ {
+ mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
+ static_cast<FTPChannelParent*>(aArgs.get_PFTPChannelParent()));
+ break;
+ }
+ default:
+ NS_NOTREACHED("unknown ChannelDiverterArgs type");
+ return false;
+ }
+ MOZ_ASSERT(mDivertableChannelParent);
+
+ nsresult rv = mDivertableChannelParent->SuspendForDiversion();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ return true;
+}
+
+void
+ChannelDiverterParent::DivertTo(nsIStreamListener* newListener)
+{
+ MOZ_ASSERT(newListener);
+ MOZ_ASSERT(mDivertableChannelParent);
+
+ mDivertableChannelParent->DivertTo(newListener);
+}
+
+void
+ChannelDiverterParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ // Implement me! Bug 1005179
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ChannelDiverterParent.h b/netwerk/base/ChannelDiverterParent.h
new file mode 100644
index 000000000..047e1c68a
--- /dev/null
+++ b/netwerk/base/ChannelDiverterParent.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _channeldiverterparent_h_
+#define _channeldiverterparent_h_
+
+#include "mozilla/net/PChannelDiverterParent.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterArgs;
+class ADivertableParentChannel;
+
+class ChannelDiverterParent :
+ public PChannelDiverterParent
+{
+public:
+ ChannelDiverterParent();
+ virtual ~ChannelDiverterParent();
+
+ bool Init(const ChannelDiverterArgs& aArgs);
+
+ void DivertTo(nsIStreamListener* newListener);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ RefPtr<ADivertableParentChannel> mDivertableChannelParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterparent_h_ */
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
new file mode 100644
index 000000000..f5d0880ae
--- /dev/null
+++ b/netwerk/base/Dashboard.cpp
@@ -0,0 +1,921 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/NetDashboardBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/net/Dashboard.h"
+#include "mozilla/net/HttpInfo.h"
+#include "nsHttp.h"
+#include "nsICancelable.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsIInputStream.h"
+#include "nsISocketTransport.h"
+#include "nsIThread.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/Logging.h"
+
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::Sequence;
+using mozilla::dom::ToJSValue;
+
+namespace mozilla {
+namespace net {
+
+class SocketData
+ : public nsISupports
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketData()
+ {
+ mTotalSent = 0;
+ mTotalRecv = 0;
+ mThread = nullptr;
+ }
+
+ uint64_t mTotalSent;
+ uint64_t mTotalRecv;
+ nsTArray<SocketInfo> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+
+private:
+ virtual ~SocketData()
+ {
+ }
+};
+
+static void GetErrorString(nsresult rv, nsAString& errorString);
+
+NS_IMPL_ISUPPORTS0(SocketData)
+
+
+class HttpData
+ : public nsISupports
+{
+ virtual ~HttpData()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HttpData()
+ {
+ mThread = nullptr;
+ }
+
+ nsTArray<HttpRetParams> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(HttpData)
+
+
+class WebSocketRequest
+ : public nsISupports
+{
+ virtual ~WebSocketRequest()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WebSocketRequest()
+ {
+ mThread = nullptr;
+ }
+
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(WebSocketRequest)
+
+
+class DnsData
+ : public nsISupports
+{
+ virtual ~DnsData()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DnsData()
+ {
+ mThread = nullptr;
+ }
+
+ nsTArray<DNSCacheEntries> mData;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+};
+
+NS_IMPL_ISUPPORTS0(DnsData)
+
+
+class ConnectionData
+ : public nsITransportEventSink
+ , public nsITimerCallback
+{
+ virtual ~ConnectionData()
+ {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITIMERCALLBACK
+
+ void StartTimer(uint32_t aTimeout);
+ void StopTimer();
+
+ explicit ConnectionData(Dashboard *target)
+ {
+ mThread = nullptr;
+ mDashboard = target;
+ }
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIInputStream> mStreamIn;
+ nsCOMPtr<nsITimer> mTimer;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+ Dashboard *mDashboard;
+
+ nsCString mHost;
+ uint32_t mPort;
+ const char *mProtocol;
+ uint32_t mTimeout;
+
+ nsString mStatus;
+};
+
+NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback)
+
+NS_IMETHODIMP
+ConnectionData::OnTransportStatus(nsITransport *aTransport, nsresult aStatus,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ StopTimer();
+ }
+
+ GetErrorString(aStatus, mStatus);
+ mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (mDashboard, &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ConnectionData::Notify(nsITimer *aTimer)
+{
+ MOZ_ASSERT(aTimer == mTimer);
+
+ if (mSocket) {
+ mSocket->Close(NS_ERROR_ABORT);
+ mSocket = nullptr;
+ mStreamIn = nullptr;
+ }
+
+ mTimer = nullptr;
+
+ mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT");
+ mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (mDashboard, &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void
+ConnectionData::StartTimer(uint32_t aTimeout)
+{
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ mTimer->InitWithCallback(this, aTimeout * 1000,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+ConnectionData::StopTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+
+class LookupHelper;
+
+class LookupArgument
+ : public nsISupports
+{
+ virtual ~LookupArgument()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LookupArgument(nsIDNSRecord *aRecord, LookupHelper *aHelper)
+ {
+ mRecord = aRecord;
+ mHelper = aHelper;
+ }
+
+ nsCOMPtr<nsIDNSRecord> mRecord;
+ RefPtr<LookupHelper> mHelper;
+};
+
+NS_IMPL_ISUPPORTS0(LookupArgument)
+
+
+class LookupHelper
+ : public nsIDNSListener
+{
+ virtual ~LookupHelper()
+ {
+ if (mCancel) {
+ mCancel->Cancel(NS_ERROR_ABORT);
+ }
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ LookupHelper() {
+ }
+
+ nsresult ConstructAnswer(LookupArgument *aArgument);
+public:
+ nsCOMPtr<nsICancelable> mCancel;
+ nsMainThreadPtrHandle<NetDashboardCallback> mCallback;
+ nsIThread *mThread;
+ nsresult mStatus;
+};
+
+NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)
+
+NS_IMETHODIMP
+LookupHelper::OnLookupComplete(nsICancelable *aRequest,
+ nsIDNSRecord *aRecord, nsresult aStatus)
+{
+ MOZ_ASSERT(aRequest == mCancel);
+ mCancel = nullptr;
+ mStatus = aStatus;
+
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mThread->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>
+ (this, &LookupHelper::ConstructAnswer, arg),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+LookupHelper::ConstructAnswer(LookupArgument *aArgument)
+{
+ nsIDNSRecord *aRecord = aArgument->mRecord;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSLookupDict dict;
+ dict.mAddress.Construct();
+
+ Sequence<nsString> &addresses = dict.mAddress.Value();
+
+ if (NS_SUCCEEDED(mStatus)) {
+ dict.mAnswer = true;
+ bool hasMore;
+ aRecord->HasMore(&hasMore);
+ while (hasMore) {
+ nsString* nextAddress = addresses.AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString nextAddressASCII;
+ aRecord->GetNextAddrAsString(nextAddressASCII);
+ CopyASCIItoUTF16(nextAddressASCII, *nextAddress);
+ aRecord->HasMore(&hasMore);
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)
+
+Dashboard::Dashboard()
+{
+ mEnableLogging = false;
+}
+
+Dashboard::~Dashboard()
+{
+}
+
+NS_IMETHODIMP
+Dashboard::RequestSockets(NetDashboardCallback *aCallback)
+{
+ RefPtr<SocketData> socketData = new SocketData();
+ socketData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ socketData->mThread = NS_GetCurrentThread();
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<SocketData>>
+ (this, &Dashboard::GetSocketsDispatch, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetSocketsDispatch(SocketData *aSocketData)
+{
+ RefPtr<SocketData> socketData = aSocketData;
+ if (gSocketTransportService) {
+ gSocketTransportService->GetSocketConnections(&socketData->mData);
+ socketData->mTotalSent = gSocketTransportService->GetSentBytes();
+ socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes();
+ }
+ socketData->mThread->Dispatch(NewRunnableMethod<RefPtr<SocketData>>
+ (this, &Dashboard::GetSockets, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetSockets(SocketData *aSocketData)
+{
+ RefPtr<SocketData> socketData = aSocketData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::SocketsDict dict;
+ dict.mSockets.Construct();
+ dict.mSent = 0;
+ dict.mReceived = 0;
+
+ Sequence<mozilla::dom::SocketElement> &sockets = dict.mSockets.Value();
+
+ uint32_t length = socketData->mData.Length();
+ if (!sockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < socketData->mData.Length(); i++) {
+ dom::SocketElement &mSocket = *sockets.AppendElement(fallible);
+ CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost);
+ mSocket.mPort = socketData->mData[i].port;
+ mSocket.mActive = socketData->mData[i].active;
+ mSocket.mTcp = socketData->mData[i].tcp;
+ mSocket.mSent = (double) socketData->mData[i].sent;
+ mSocket.mReceived = (double) socketData->mData[i].received;
+ dict.mSent += socketData->mData[i].sent;
+ dict.mReceived += socketData->mData[i].received;
+ }
+
+ dict.mSent += socketData->mTotalSent;
+ dict.mReceived += socketData->mTotalRecv;
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val))
+ return NS_ERROR_FAILURE;
+ socketData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestHttpConnections(NetDashboardCallback *aCallback)
+{
+ RefPtr<HttpData> httpData = new HttpData();
+ httpData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ httpData->mThread = NS_GetCurrentThread();
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>
+ (this, &Dashboard::GetHttpDispatch, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetHttpDispatch(HttpData *aHttpData)
+{
+ RefPtr<HttpData> httpData = aHttpData;
+ HttpInfo::GetHttpConnectionData(&httpData->mData);
+ httpData->mThread->Dispatch(NewRunnableMethod<RefPtr<HttpData>>
+ (this, &Dashboard::GetHttpConnections, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+
+nsresult
+Dashboard::GetHttpConnections(HttpData *aHttpData)
+{
+ RefPtr<HttpData> httpData = aHttpData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HttpConnDict dict;
+ dict.mConnections.Construct();
+
+ using mozilla::dom::HalfOpenInfoDict;
+ using mozilla::dom::HttpConnectionElement;
+ using mozilla::dom::HttpConnInfo;
+ Sequence<HttpConnectionElement> &connections = dict.mConnections.Value();
+
+ uint32_t length = httpData->mData.Length();
+ if (!connections.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
+ HttpConnectionElement &connection = *connections.AppendElement(fallible);
+
+ CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
+ connection.mPort = httpData->mData[i].port;
+ connection.mSpdy = httpData->mData[i].spdy;
+ connection.mSsl = httpData->mData[i].ssl;
+
+ connection.mActive.Construct();
+ connection.mIdle.Construct();
+ connection.mHalfOpens.Construct();
+
+ Sequence<HttpConnInfo> &active = connection.mActive.Value();
+ Sequence<HttpConnInfo> &idle = connection.mIdle.Value();
+ Sequence<HalfOpenInfoDict> &halfOpens = connection.mHalfOpens.Value();
+
+ if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
+ !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
+ !halfOpens.SetCapacity(httpData->mData[i].halfOpens.Length(),
+ fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
+ HttpConnInfo &info = *active.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].active[j].rtt;
+ info.mTtl = httpData->mData[i].active[j].ttl;
+ info.mProtocolVersion =
+ httpData->mData[i].active[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
+ HttpConnInfo &info = *idle.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].idle[j].rtt;
+ info.mTtl = httpData->mData[i].idle[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].halfOpens.Length(); j++) {
+ HalfOpenInfoDict &info = *halfOpens.AppendElement(fallible);
+ info.mSpeculative = httpData->mData[i].halfOpens[j].speculative;
+ }
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ httpData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::GetEnableLogging(bool *value)
+{
+ *value = mEnableLogging;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::SetEnableLogging(const bool value)
+{
+ mEnableLogging = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ LogData mData(nsCString(aHost), aSerial, aEncrypted);
+ if (mWs.data.Contains(mData)) {
+ return NS_OK;
+ }
+ if (!mWs.data.AppendElement(mData)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data.RemoveElementAt(index);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, uint32_t aLength)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgSent++;
+ mWs.data[index].mSizeSent += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, uint32_t aLength)
+{
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1)
+ return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgReceived++;
+ mWs.data[index].mSizeReceived += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestWebsocketConnections(NetDashboardCallback *aCallback)
+{
+ RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest();
+ wsRequest->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ wsRequest->mThread = NS_GetCurrentThread();
+
+ wsRequest->mThread->Dispatch(NewRunnableMethod<RefPtr<WebSocketRequest>>
+ (this, &Dashboard::GetWebSocketConnections, wsRequest),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetWebSocketConnections(WebSocketRequest *aWsRequest)
+{
+ RefPtr<WebSocketRequest> wsRequest = aWsRequest;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::WebSocketDict dict;
+ dict.mWebsockets.Construct();
+ Sequence<mozilla::dom::WebSocketElement> &websockets =
+ dict.mWebsockets.Value();
+
+ mozilla::MutexAutoLock lock(mWs.lock);
+ uint32_t length = mWs.data.Length();
+ if (!websockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < mWs.data.Length(); i++) {
+ dom::WebSocketElement &websocket = *websockets.AppendElement(fallible);
+ CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport);
+ websocket.mMsgsent = mWs.data[i].mMsgSent;
+ websocket.mMsgreceived = mWs.data[i].mMsgReceived;
+ websocket.mSentsize = mWs.data[i].mSizeSent;
+ websocket.mReceivedsize = mWs.data[i].mSizeReceived;
+ websocket.mEncrypted = mWs.data[i].mEncrypted;
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ wsRequest->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSInfo(NetDashboardCallback *aCallback)
+{
+ RefPtr<DnsData> dnsData = new DnsData();
+ dnsData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+
+ nsresult rv;
+ dnsData->mData.Clear();
+ dnsData->mThread = NS_GetCurrentThread();
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<DnsData>>
+ (this, &Dashboard::GetDnsInfoDispatch, dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetDnsInfoDispatch(DnsData *aDnsData)
+{
+ RefPtr<DnsData> dnsData = aDnsData;
+ if (mDnsService) {
+ mDnsService->GetDNSCacheEntries(&dnsData->mData);
+ }
+ dnsData->mThread->Dispatch(NewRunnableMethod<RefPtr<DnsData>>
+ (this, &Dashboard::GetDNSCacheEntries, dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetDNSCacheEntries(DnsData *dnsData)
+{
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSCacheDict dict;
+ dict.mEntries.Construct();
+ Sequence<mozilla::dom::DnsCacheEntry> &entries = dict.mEntries.Value();
+
+ uint32_t length = dnsData->mData.Length();
+ if (!entries.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < dnsData->mData.Length(); i++) {
+ dom::DnsCacheEntry &entry = *entries.AppendElement(fallible);
+ entry.mHostaddr.Construct();
+
+ Sequence<nsString> &addrs = entry.mHostaddr.Value();
+ if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname);
+ entry.mExpiration = dnsData->mData[i].expiration;
+
+ for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) {
+ nsString* addr = addrs.AppendElement(fallible);
+ if (!addr) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr);
+ }
+
+ if (dnsData->mData[i].family == PR_AF_INET6) {
+ CopyASCIItoUTF16("ipv6", entry.mFamily);
+ } else {
+ CopyASCIItoUTF16("ipv4", entry.mFamily);
+ }
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString &aHost,
+ NetDashboardCallback *aCallback)
+{
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ helper->mThread = NS_GetCurrentThread();
+ rv = mDnsService->AsyncResolve(aHost, 0, helper.get(),
+ NS_GetCurrentThread(),
+ getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+void
+HttpConnInfo::SetHTTP1ProtocolVersion(uint8_t pv)
+{
+ switch (pv) {
+ case NS_HTTP_VERSION_0_9:
+ protocolVersion.AssignLiteral(u"http/0.9");
+ break;
+ case NS_HTTP_VERSION_1_0:
+ protocolVersion.AssignLiteral(u"http/1.0");
+ break;
+ case NS_HTTP_VERSION_1_1:
+ protocolVersion.AssignLiteral(u"http/1.1");
+ break;
+ case NS_HTTP_VERSION_2_0:
+ protocolVersion.AssignLiteral(u"http/2.0");
+ break;
+ default:
+ protocolVersion.AssignLiteral(u"unknown protocol version");
+ }
+}
+
+void
+HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv)
+{
+ MOZ_ASSERT (pv == HTTP_VERSION_2);
+ protocolVersion.Assign(u"h2");
+}
+
+NS_IMETHODIMP
+Dashboard::GetLogPath(nsACString &aLogPath)
+{
+ aLogPath.SetCapacity(2048);
+ uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048);
+ aLogPath.SetLength(len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort,
+ const char *aProtocol, uint32_t aTimeout,
+ NetDashboardCallback *aCallback)
+{
+ nsresult rv;
+ RefPtr<ConnectionData> connectionData = new ConnectionData(this);
+ connectionData->mHost = aHost;
+ connectionData->mPort = aPort;
+ connectionData->mProtocol = aProtocol;
+ connectionData->mTimeout = aTimeout;
+
+ connectionData->mCallback =
+ new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true);
+ connectionData->mThread = NS_GetCurrentThread();
+
+ rv = TestNewConnection(connectionData);
+ if (NS_FAILED(rv)) {
+ mozilla::net::GetErrorString(rv, connectionData->mStatus);
+ connectionData->mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>
+ (this, &Dashboard::GetConnectionStatus, connectionData),
+ NS_DISPATCH_NORMAL);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Dashboard::GetConnectionStatus(ConnectionData *aConnectionData)
+{
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::ConnStatusDict dict;
+ dict.mStatus = connectionData->mStatus;
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val))
+ return NS_ERROR_FAILURE;
+
+ connectionData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+nsresult
+Dashboard::TestNewConnection(ConnectionData *aConnectionData)
+{
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+
+ nsresult rv;
+ if (!connectionData->mHost.Length() ||
+ !net_IsValidHostName(connectionData->mHost)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (connectionData->mProtocol &&
+ NS_LITERAL_STRING("ssl").EqualsASCII(connectionData->mProtocol)) {
+ rv = gSocketTransportService->CreateTransport(
+ &connectionData->mProtocol, 1, connectionData->mHost,
+ connectionData->mPort, nullptr,
+ getter_AddRefs(connectionData->mSocket));
+ } else {
+ rv = gSocketTransportService->CreateTransport(
+ nullptr, 0, connectionData->mHost,
+ connectionData->mPort, nullptr,
+ getter_AddRefs(connectionData->mSocket));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->SetEventSink(connectionData,
+ NS_GetCurrentThread());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->OpenInputStream(
+ nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(connectionData->mStreamIn));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ connectionData->StartTimer(connectionData->mTimeout);
+
+ return rv;
+}
+
+typedef struct
+{
+ nsresult key;
+ const char *error;
+} ErrorEntry;
+
+#undef ERROR
+#define ERROR(key, val) {key, #key}
+
+ErrorEntry socketTransportStatuses[] = {
+ ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
+ ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
+ ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
+ ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
+ ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
+ ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
+ ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
+};
+#undef ERROR
+
+
+static void
+GetErrorString(nsresult rv, nsAString& errorString)
+{
+ for (size_t i = 0; i < ArrayLength(socketTransportStatuses); ++i) {
+ if (socketTransportStatuses[i].key == rv) {
+ errorString.AssignASCII(socketTransportStatuses[i].error);
+ return;
+ }
+ }
+ nsAutoCString errorCString;
+ mozilla::GetErrorName(rv, errorCString);
+ CopyUTF8toUTF16(errorCString, errorString);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h
new file mode 100644
index 000000000..4e893c15d
--- /dev/null
+++ b/netwerk/base/Dashboard.h
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDashboard_h__
+#define nsDashboard_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsIDashboard.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIDNSListener.h"
+#include "nsIServiceManager.h"
+#include "nsITimer.h"
+#include "nsITransport.h"
+
+class nsIDNSService;
+
+namespace mozilla {
+namespace net {
+
+class SocketData;
+class HttpData;
+class DnsData;
+class WebSocketRequest;
+class ConnectionData;
+
+class Dashboard final
+ : public nsIDashboard
+ , public nsIDashboardEventNotifier
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDASHBOARD
+ NS_DECL_NSIDASHBOARDEVENTNOTIFIER
+
+ Dashboard();
+ static const char *GetErrorString(nsresult rv);
+ nsresult GetConnectionStatus(ConnectionData *aConnectionData);
+
+private:
+
+ struct LogData
+ {
+ LogData(nsCString host, uint32_t serial, bool encryption):
+ mHost(host),
+ mSerial(serial),
+ mMsgSent(0),
+ mMsgReceived(0),
+ mSizeSent(0),
+ mSizeReceived(0),
+ mEncrypted(encryption)
+ { }
+ nsCString mHost;
+ uint32_t mSerial;
+ uint32_t mMsgSent;
+ uint32_t mMsgReceived;
+ uint64_t mSizeSent;
+ uint64_t mSizeReceived;
+ bool mEncrypted;
+ bool operator==(const LogData& a) const
+ {
+ return mHost.Equals(a.mHost) && (mSerial == a.mSerial);
+ }
+ };
+
+ struct WebSocketData
+ {
+ WebSocketData():lock("Dashboard.webSocketData")
+ {
+ }
+ uint32_t IndexOf(nsCString hostname, uint32_t mSerial)
+ {
+ LogData temp(hostname, mSerial, false);
+ return data.IndexOf(temp);
+ }
+ nsTArray<LogData> data;
+ mozilla::Mutex lock;
+ };
+
+
+ bool mEnableLogging;
+ WebSocketData mWs;
+
+private:
+ virtual ~Dashboard();
+
+ nsresult GetSocketsDispatch(SocketData *);
+ nsresult GetHttpDispatch(HttpData *);
+ nsresult GetDnsInfoDispatch(DnsData *);
+ nsresult TestNewConnection(ConnectionData *);
+
+ /* Helper methods that pass the JSON to the callback function. */
+ nsresult GetSockets(SocketData *);
+ nsresult GetHttpConnections(HttpData *);
+ nsresult GetDNSCacheEntries(DnsData *);
+ nsresult GetWebSocketConnections(WebSocketRequest *);
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsDashboard_h__
diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h
new file mode 100644
index 000000000..3f1f30d16
--- /dev/null
+++ b/netwerk/base/DashboardTypes.h
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_DashboardTypes_h_
+#define mozilla_net_DashboardTypes_h_
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+struct SocketInfo
+{
+ nsCString host;
+ uint64_t sent;
+ uint64_t received;
+ uint16_t port;
+ bool active;
+ bool tcp;
+};
+
+struct HalfOpenSockets
+{
+ bool speculative;
+};
+
+struct DNSCacheEntries
+{
+ nsCString hostname;
+ nsTArray<nsCString> hostaddr;
+ uint16_t family;
+ int64_t expiration;
+ nsCString netInterface;
+};
+
+struct HttpConnInfo
+{
+ uint32_t ttl;
+ uint32_t rtt;
+ nsString protocolVersion;
+
+ void SetHTTP1ProtocolVersion(uint8_t pv);
+ void SetHTTP2ProtocolVersion(uint8_t pv);
+};
+
+struct HttpRetParams
+{
+ nsCString host;
+ nsTArray<HttpConnInfo> active;
+ nsTArray<HttpConnInfo> idle;
+ nsTArray<HalfOpenSockets> halfOpens;
+ uint32_t counter;
+ uint16_t port;
+ bool spdy;
+ bool ssl;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DashboardTypes_h_
diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp
new file mode 100644
index 000000000..e12624ea2
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.cpp
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTokenBucket.h"
+
+#include "nsICancelable.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#ifdef DEBUG
+#include "MainThreadUtils.h"
+#endif
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <mmsystem.h>
+#endif
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////
+// EventTokenBucketCancelable
+////////////////////////////////////////////
+
+class TokenBucketCancelable : public nsICancelable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit TokenBucketCancelable(class ATokenBucketEvent *event);
+ void Fire();
+
+private:
+ virtual ~TokenBucketCancelable() {}
+
+ friend class EventTokenBucket;
+ ATokenBucketEvent *mEvent;
+};
+
+NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable)
+
+TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event)
+ : mEvent(event)
+{
+}
+
+NS_IMETHODIMP
+TokenBucketCancelable::Cancel(nsresult reason)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mEvent = nullptr;
+ return NS_OK;
+}
+
+void
+TokenBucketCancelable::Fire()
+{
+ if (!mEvent)
+ return;
+
+ ATokenBucketEvent *event = mEvent;
+ mEvent = nullptr;
+ event->OnTokenBucketAdmitted();
+}
+
+////////////////////////////////////////////
+// EventTokenBucket
+////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback)
+
+// by default 1hz with no burst
+EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond,
+ uint32_t burstSize)
+ : mUnitCost(kUsecPerSec)
+ , mMaxCredit(kUsecPerSec)
+ , mCredit(kUsecPerSec)
+ , mPaused(false)
+ , mStopped(false)
+ , mTimerArmed(false)
+#ifdef XP_WIN
+ , mFineGrainTimerInUse(false)
+ , mFineGrainResetTimerArmed(false)
+#endif
+{
+ mLastUpdate = TimeStamp::Now();
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer)
+ mTimer->SetTarget(sts);
+ SetRate(eventsPerSecond, burstSize);
+}
+
+EventTokenBucket::~EventTokenBucket()
+{
+ SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n",
+ this, mEvents.GetSize()));
+
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ cancelable->Fire();
+ }
+}
+
+void
+EventTokenBucket::CleanupTimers()
+{
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mTimerArmed = false;
+
+#ifdef XP_WIN
+ NormalTimers();
+ if (mFineGrainResetTimer && mFineGrainResetTimerArmed) {
+ mFineGrainResetTimer->Cancel();
+ }
+ mFineGrainResetTimer = nullptr;
+ mFineGrainResetTimerArmed = false;
+#endif
+}
+
+void
+EventTokenBucket::SetRate(uint32_t eventsPerSecond,
+ uint32_t burstSize)
+{
+ SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n",
+ this, eventsPerSecond, burstSize));
+
+ if (eventsPerSecond > kMaxHz) {
+ eventsPerSecond = kMaxHz;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ if (!eventsPerSecond) {
+ eventsPerSecond = 1;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ mUnitCost = kUsecPerSec / eventsPerSecond;
+ mMaxCredit = mUnitCost * burstSize;
+ if (mMaxCredit > kUsecPerSec * 60 * 15) {
+ SOCKET_LOG((" burstSize out of range\n"));
+ mMaxCredit = kUsecPerSec * 60 * 15;
+ }
+ mCredit = mMaxCredit;
+ mLastUpdate = TimeStamp::Now();
+}
+
+void
+EventTokenBucket::ClearCredits()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this));
+ mCredit = 0;
+}
+
+uint32_t
+EventTokenBucket::BurstEventsAvailable()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return static_cast<uint32_t>(mCredit / mUnitCost);
+}
+
+uint32_t
+EventTokenBucket::QueuedEvents()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ return mEvents.GetSize();
+}
+
+void
+EventTokenBucket::Pause()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::Pause %p\n", this));
+ if (mPaused || mStopped)
+ return;
+
+ mPaused = true;
+ if (mTimerArmed) {
+ mTimer->Cancel();
+ mTimerArmed = false;
+ }
+}
+
+void
+EventTokenBucket::UnPause()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this));
+ if (!mPaused || mStopped)
+ return;
+
+ mPaused = false;
+ DispatchEvents();
+ UpdateTimer();
+}
+
+void
+EventTokenBucket::Stop()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed));
+ mStopped = true;
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ cancelable->Fire();
+ }
+}
+
+nsresult
+EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this));
+
+ if (mStopped || !mTimer)
+ return NS_ERROR_FAILURE;
+
+ UpdateCredits();
+
+ RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event);
+ // When this function exits the cancelEvent needs 2 references, one for the
+ // mEvents queue and one for the caller of SubmitEvent()
+
+ NS_ADDREF(*cancelable = cancelEvent.get());
+
+ if (mPaused || !TryImmediateDispatch(cancelEvent.get())) {
+ // queue it
+ SOCKET_LOG((" queued\n"));
+ mEvents.Push(cancelEvent.forget().take());
+ UpdateTimer();
+ }
+ else {
+ SOCKET_LOG((" dispatched synchronously\n"));
+ }
+
+ return NS_OK;
+}
+
+bool
+EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable)
+{
+ if (mCredit < mUnitCost)
+ return false;
+
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ return true;
+}
+
+void
+EventTokenBucket::DispatchEvents()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused));
+ if (mPaused || mStopped)
+ return;
+
+ while (mEvents.GetSize() && mUnitCost <= mCredit) {
+ RefPtr<TokenBucketCancelable> cancelable =
+ dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront()));
+ if (cancelable->mEvent) {
+ SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] "
+ "Dispatching queue token bucket event cost=%lu credit=%lu\n",
+ this, mUnitCost, mCredit));
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ }
+ }
+
+#ifdef XP_WIN
+ if (!mEvents.GetSize())
+ WantNormalTimers();
+#endif
+}
+
+void
+EventTokenBucket::UpdateTimer()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer)
+ return;
+
+ if (mCredit >= mUnitCost)
+ return;
+
+ // determine the time needed to wait to accumulate enough credits to admit
+ // one more event and set the timer for that point. Always round it
+ // up because firing early doesn't help.
+ //
+ uint64_t deficit = mUnitCost - mCredit;
+ uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec;
+
+ if (msecWait < 4) // minimum wait
+ msecWait = 4;
+ else if (msecWait > 60000) // maximum wait
+ msecWait = 60000;
+
+#ifdef XP_WIN
+ FineGrainTimers();
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::UpdateTimer %p for %dms\n",
+ this, msecWait));
+ nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait),
+ nsITimer::TYPE_ONE_SHOT);
+ mTimerArmed = NS_SUCCEEDED(rv);
+}
+
+NS_IMETHODIMP
+EventTokenBucket::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+#ifdef XP_WIN
+ if (timer == mFineGrainResetTimer) {
+ FineGrainResetTimerNotify();
+ return NS_OK;
+ }
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this));
+ mTimerArmed = false;
+ if (mStopped)
+ return NS_OK;
+
+ UpdateCredits();
+ DispatchEvents();
+ UpdateTimer();
+
+ return NS_OK;
+}
+
+void
+EventTokenBucket::UpdateCredits()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsed = now - mLastUpdate;
+ mLastUpdate = now;
+
+ mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds());
+ if (mCredit > mMaxCredit)
+ mCredit = mMaxCredit;
+ SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %lu (%lu each.. %3.2f)\n",
+ this, mCredit, mUnitCost, (double)mCredit / mUnitCost));
+}
+
+#ifdef XP_WIN
+void
+EventTokenBucket::FineGrainTimers()
+{
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n",
+ this, mFineGrainTimerInUse));
+
+ mLastFineGrainTimerUse = TimeStamp::Now();
+
+ if (mFineGrainTimerInUse)
+ return;
+
+ if (mUnitCost > kCostFineGrainThreshold)
+ return;
+
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n",
+ this));
+
+ mFineGrainTimerInUse = true;
+ timeBeginPeriod(1);
+}
+
+void
+EventTokenBucket::NormalTimers()
+{
+ if (!mFineGrainTimerInUse)
+ return;
+ mFineGrainTimerInUse = false;
+
+ SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this));
+ timeEndPeriod(1);
+}
+
+void
+EventTokenBucket::WantNormalTimers()
+{
+ if (!mFineGrainTimerInUse)
+ return;
+ if (mFineGrainResetTimerArmed)
+ return;
+
+ TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse);
+ static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5);
+
+ if (elapsed >= fiveSeconds) {
+ NormalTimers();
+ return;
+ }
+
+ if (!mFineGrainResetTimer)
+ mFineGrainResetTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ // if we can't delay the reset, just do it now
+ if (!mFineGrainResetTimer) {
+ NormalTimers();
+ return;
+ }
+
+ // pad the callback out 100ms to avoid having to round trip this again if the
+ // timer calls back just a tad early.
+ SOCKET_LOG(("EventTokenBucket::WantNormalTimers %p "
+ "Will reset timer granularity after delay", this));
+
+ mFineGrainResetTimer->InitWithCallback(
+ this,
+ static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100,
+ nsITimer::TYPE_ONE_SHOT);
+ mFineGrainResetTimerArmed = true;
+}
+
+void
+EventTokenBucket::FineGrainResetTimerNotify()
+{
+ SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n",
+ this, mEvents.GetSize()));
+ mFineGrainResetTimerArmed = false;
+
+ // If we are currently processing events then wait for the queue to drain
+ // before trying to reset back to normal timers again
+ if (!mEvents.GetSize())
+ WantNormalTimers();
+}
+
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h
new file mode 100644
index 000000000..b187ca7b0
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetEventTokenBucket_h__
+#define NetEventTokenBucket_h__
+
+#include "ARefBase.h"
+#include "nsCOMPtr.h"
+#include "nsDeque.h"
+#include "nsITimer.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICancelable;
+
+namespace mozilla {
+namespace net {
+
+/* A token bucket is used to govern the maximum rate a series of events
+ can be executed at. For instance if your event was "eat a piece of cake"
+ then a token bucket configured to allow "1 piece per day" would spread
+ the eating of a 8 piece cake over 8 days even if you tried to eat the
+ whole thing up front. In a practical sense it 'costs' 1 token to execute
+ an event and tokens are 'earned' at a particular rate as time goes by.
+
+ The token bucket can be perfectly smooth or allow a configurable amount of
+ burstiness. A bursty token bucket allows you to save up unused credits, while
+ a perfectly smooth one would not. A smooth "1 per day" cake token bucket
+ would require 9 days to eat that cake if you skipped a slice on day 4
+ (use the token or lose it), while a token bucket configured with a burst
+ of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day
+ 5) and finish the cake in the usual 8 days.
+
+ EventTokenBucket(hz=20, burst=5) creates a token bucket with the following properties:
+
+ + events from an infinite stream will be admitted 20 times per second (i.e.
+ hz=20 means 1 event per 50 ms). Timers will be used to space things evenly down to
+ 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than 200hz will admit
+ multiple events with 5ms gaps between them. 10000hz is the maximum rate and 1hz is
+ the minimum rate.
+
+ + The burst size controls the limit of 'credits' that a token bucket can accumulate
+ when idle. For our (20,5) example each event requires 50ms of credit (again, 20hz = 50ms
+ per event). a burst size of 5 means that the token bucket can accumulate a
+ maximum of 250ms (5 * 50ms) for this bucket. If no events have been admitted for the
+ last full second the bucket can still only accumulate 250ms of credit - but that credit
+ means that 5 events can be admitted without delay. A burst size of 1 is the minimum.
+ The EventTokenBucket is created with maximum credits already applied, but they
+ can be cleared with the ClearCredits() method. The maximum burst size is
+ 15 minutes worth of events.
+
+ + An event is submitted to the token bucket asynchronously through SubmitEvent().
+ The OnTokenBucketAdmitted() method of the submitted event is used as a callback
+ when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller
+ for use in the case they do not wish to wait for the callback.
+*/
+
+class EventTokenBucket;
+
+class ATokenBucketEvent
+{
+public:
+ virtual void OnTokenBucketAdmitted() = 0;
+};
+
+class TokenBucketCancelable;
+
+class EventTokenBucket : public nsITimerCallback, public ARefBase
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ // This should be constructed on the main thread
+ EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ // These public methods are all meant to be called from the socket thread
+ void ClearCredits();
+ uint32_t BurstEventsAvailable();
+ uint32_t QueuedEvents();
+
+ // a paused token bucket will not process any events, but it will accumulate
+ // credits. ClearCredits can be used before unpausing if desired.
+ void Pause();
+ void UnPause();
+ void Stop();
+
+ // The returned cancelable event can only be canceled from the socket thread
+ nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable);
+
+private:
+ virtual ~EventTokenBucket();
+ void CleanupTimers();
+
+ friend class RunNotifyEvent;
+ friend class SetTimerEvent;
+
+ bool TryImmediateDispatch(TokenBucketCancelable *event);
+ void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ void DispatchEvents();
+ void UpdateTimer();
+ void UpdateCredits();
+
+ const static uint64_t kUsecPerSec = 1000000;
+ const static uint64_t kUsecPerMsec = 1000;
+ const static uint64_t kMaxHz = 10000;
+
+ uint64_t mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond)
+ uint64_t mMaxCredit; // usec mCredit limit (from busrtSize)
+ uint64_t mCredit; // usec of accumulated credit.
+
+ bool mPaused;
+ bool mStopped;
+ nsDeque mEvents;
+ bool mTimerArmed;
+ TimeStamp mLastUpdate;
+
+ // The timer is created on the main thread, but is armed and executes Notify()
+ // callbacks on the socket thread in order to maintain low latency of event
+ // delivery.
+ nsCOMPtr<nsITimer> mTimer;
+
+#ifdef XP_WIN
+ // Windows timers are 15ms granularity by default. When we have active events
+ // that need to be dispatched at 50ms or less granularity we change the OS
+ // granularity to 1ms. 90 seconds after that need has elapsed we will change it
+ // back
+ const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec;
+
+ void FineGrainTimers(); // get 1ms granularity
+ void NormalTimers(); // reset to default granularity
+ void WantNormalTimers(); // reset after 90 seconds if not needed in interim
+ void FineGrainResetTimerNotify(); // delayed callback to reset
+
+ TimeStamp mLastFineGrainTimerUse;
+ bool mFineGrainTimerInUse;
+ bool mFineGrainResetTimerArmed;
+ nsCOMPtr<nsITimer> mFineGrainResetTimer;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
new file mode 100644
index 000000000..61b9394f9
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::dom;
+namespace mozilla {
+namespace net {
+
+// LoadContextInfo
+
+NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo)
+
+LoadContextInfo::LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes)
+ : mIsAnonymous(aIsAnonymous)
+ , mOriginAttributes(aOriginAttributes)
+{
+}
+
+LoadContextInfo::~LoadContextInfo()
+{
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool *aIsPrivate)
+{
+ *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous)
+{
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+NeckoOriginAttributes const* LoadContextInfo::OriginAttributesPtr()
+{
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetOriginAttributes(JSContext *aCx,
+ JS::MutableHandle<JS::Value> aVal)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// LoadContextInfoFactory
+
+NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory)
+
+NS_IMETHODIMP LoadContextInfoFactory::GetDefault(nsILoadContextInfo * *aDefault)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, NeckoOriginAttributes());
+ info.forget(aDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(nsILoadContextInfo * *aPrivate)
+{
+ NeckoOriginAttributes attrs;
+ attrs.SyncAttributesWithPrivateBrowsing(true);
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, attrs);
+ info.forget(aPrivate);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous(nsILoadContextInfo * *aAnonymous)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(true, NeckoOriginAttributes());
+ info.forget(aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::Custom(bool aAnonymous,
+ JS::HandleValue aOriginAttributes, JSContext *cx,
+ nsILoadContextInfo * *_retval)
+{
+ NeckoOriginAttributes attrs;
+ bool status = attrs.Init(cx, aOriginAttributes);
+ NS_ENSURE_TRUE(status, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aAnonymous, attrs);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext(nsILoadContext *aLoadContext, bool aAnonymous,
+ nsILoadContextInfo * *_retval)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aLoadContext, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow *aWindow, bool aAnonymous,
+ nsILoadContextInfo * *_retval)
+{
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aWindow, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+// Helper functions
+
+LoadContextInfo *
+GetLoadContextInfo(nsIChannel * aChannel)
+{
+ nsresult rv;
+
+ DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel);
+
+ bool anon = false;
+ nsLoadFlags loadFlags;
+ rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_SUCCEEDED(rv)) {
+ anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS);
+ }
+
+ NeckoOriginAttributes oa;
+ NS_GetOriginAttributes(aChannel, oa);
+ MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+
+ return new LoadContextInfo(anon, oa);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContext *aLoadContext, bool aIsAnonymous)
+{
+ if (!aLoadContext) {
+ return new LoadContextInfo(aIsAnonymous,
+ NeckoOriginAttributes(nsILoadContextInfo::NO_APP_ID, false));
+ }
+
+ DebugOnly<bool> pb = aLoadContext->UsePrivateBrowsing();
+ DocShellOriginAttributes doa;
+ aLoadContext->GetOriginAttributes(doa);
+ MOZ_ASSERT(pb == (doa.mPrivateBrowsingId > 0));
+
+ NeckoOriginAttributes noa;
+ noa.InheritFromDocShellToNecko(doa);
+
+ return new LoadContextInfo(aIsAnonymous, noa);
+}
+
+LoadContextInfo*
+GetLoadContextInfo(nsIDOMWindow *aWindow,
+ bool aIsAnonymous)
+{
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+
+ return GetLoadContextInfo(loadContext, aIsAnonymous);
+}
+
+LoadContextInfo *
+GetLoadContextInfo(nsILoadContextInfo *aInfo)
+{
+ return new LoadContextInfo(aInfo->IsAnonymous(),
+ *aInfo->OriginAttributesPtr());
+}
+
+LoadContextInfo *
+GetLoadContextInfo(bool const aIsAnonymous,
+ NeckoOriginAttributes const &aOriginAttributes)
+{
+ return new LoadContextInfo(aIsAnonymous,
+ aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h
new file mode 100644
index 000000000..8477dfd1c
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.h
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadContextInfo_h__
+#define nsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+namespace mozilla {
+namespace net {
+
+class LoadContextInfo : public nsILoadContextInfo
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes);
+
+private:
+ virtual ~LoadContextInfo();
+
+protected:
+ bool mIsAnonymous : 1;
+ NeckoOriginAttributes mOriginAttributes;
+};
+
+class LoadContextInfoFactory : public nsILoadContextInfoFactory
+{
+ virtual ~LoadContextInfoFactory() {}
+public:
+ NS_DECL_ISUPPORTS // deliberately not thread-safe
+ NS_DECL_NSILOADCONTEXTINFOFACTORY
+};
+
+LoadContextInfo*
+GetLoadContextInfo(nsIChannel *aChannel);
+
+LoadContextInfo*
+GetLoadContextInfo(nsILoadContext *aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo*
+GetLoadContextInfo(nsIDOMWindow *aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo*
+GetLoadContextInfo(nsILoadContextInfo *aInfo);
+
+LoadContextInfo*
+GetLoadContextInfo(bool const aIsAnonymous,
+ NeckoOriginAttributes const &aOriginAttributes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
new file mode 100644
index 000000000..216cf559c
--- /dev/null
+++ b/netwerk/base/LoadInfo.cpp
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/LoadInfo.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsFrameLoader.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIFrameLoader.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "nsNullPrincipal.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+static void
+InheritOriginAttributes(nsIPrincipal* aLoadingPrincipal, NeckoOriginAttributes& aAttrs)
+{
+ const PrincipalOriginAttributes attrs =
+ BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef();
+ aAttrs.InheritFromDocToNecko(attrs);
+}
+
+LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType)
+ : mLoadingPrincipal(aLoadingContext ?
+ aLoadingContext->NodePrincipal() : aLoadingPrincipal)
+ , mTriggeringPrincipal(aTriggeringPrincipal ?
+ aTriggeringPrincipal : mLoadingPrincipal.get())
+ , mPrincipalToInherit(nullptr)
+ , mLoadingContext(do_GetWeakReference(aLoadingContext))
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(aContentPolicyType)
+ , mTainting(LoadTainting::Basic)
+ , mUpgradeInsecureRequests(false)
+ , mVerifySignedContent(false)
+ , mEnforceSRI(false)
+ , mForceInheritPrincipalDropped(false)
+ , mInnerWindowID(0)
+ , mOuterWindowID(0)
+ , mParentOuterWindowID(0)
+ , mFrameOuterWindowID(0)
+ , mEnforceSecurity(false)
+ , mInitialSecurityCheckDone(false)
+ , mIsThirdPartyContext(false)
+ , mForcePreflight(false)
+ , mIsPreflight(false)
+ , mForceHSTSPriming(false)
+ , mMixedContentWouldBlock(false)
+{
+ MOZ_ASSERT(mLoadingPrincipal);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+#ifdef DEBUG
+ // TYPE_DOCUMENT loads initiated by javascript tests will go through
+ // nsIOService and use the wrong constructor. Don't enforce the
+ // !TYPE_DOCUMENT check in those cases
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't
+ // have a loadingPrincipal
+ MOZ_ASSERT(skipContentTypeCheck ||
+ mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT);
+
+ // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false meaning
+ // that consumers of LoadInfo that don't pass a context or pass a context from
+ // which we can't find a window will default to assuming that they're 1st
+ // party. It would be nice if we could default "safe" and assume that we are
+ // 3rd party until proven otherwise.
+
+ // if consumers pass both, aLoadingContext and aLoadingPrincipal
+ // then the loadingPrincipal must be the same as the node's principal
+ MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal ||
+ aLoadingContext->NodePrincipal() == aLoadingPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+ mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ mForceInheritPrincipalDropped = true;
+ }
+
+ if (aLoadingContext) {
+ nsCOMPtr<nsPIDOMWindowOuter> contextOuter = aLoadingContext->OwnerDoc()->GetWindow();
+ if (contextOuter) {
+ ComputeIsThirdPartyContext(contextOuter);
+ mOuterWindowID = contextOuter->WindowID();
+ nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent();
+ mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID;
+ }
+
+ mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID();
+
+ // When the element being loaded is a frame, we choose the frame's window
+ // for the window ID and the frame element's window as the parent
+ // window. This is the behavior that Chrome exposes to add-ons.
+ // NB: If the frameLoaderOwner doesn't have a frame loader, then the load
+ // must be coming from an object (such as a plugin) that's loaded into it
+ // instead of a document being loaded. In that case, treat this object like
+ // any other non-document-loading element.
+ nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(aLoadingContext);
+ nsCOMPtr<nsIFrameLoader> fl = frameLoaderOwner ?
+ frameLoaderOwner->GetFrameLoader() : nullptr;
+ if (fl) {
+ nsCOMPtr<nsIDocShell> docShell;
+ if (NS_SUCCEEDED(fl->GetDocShell(getter_AddRefs(docShell))) && docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell);
+ if (outerWindow) {
+ mFrameOuterWindowID = outerWindow->WindowID();
+ }
+ }
+ }
+
+ // if the document forces all requests to be upgraded from http to https, then
+ // we should do that for all requests. If it only forces preloads to be upgraded
+ // then we should enforce upgrade insecure requests only for preloads.
+ mUpgradeInsecureRequests =
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true));
+
+ // if owner doc has content signature, we enforce SRI
+ nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel();
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo) {
+ mEnforceSRI = loadInfo->GetVerifySignedContent();
+ }
+ }
+ }
+
+ // If CSP requires SRI (require-sri-for), then store that information
+ // in the loadInfo so we can enforce SRI before loading the subresource.
+ if (!mEnforceSRI) {
+ // do not look into the CSP if already true:
+ // a CSP saying that SRI isn't needed should not
+ // overrule GetVerifySignedContent
+ if (aLoadingPrincipal) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ aLoadingPrincipal->GetCsp(getter_AddRefs(csp));
+ uint32_t externalType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
+ // csp could be null if loading principal is system principal
+ if (csp) {
+ csp->RequireSRIForType(externalType, &mEnforceSRI);
+ }
+ // if CSP is delivered via a meta tag, it's speculatively available
+ // as 'preloadCSP'. If we are preloading a script or style, we have
+ // to apply that speculative 'preloadCSP' for such loads.
+ if (!mEnforceSRI && nsContentUtils::IsPreloadType(aContentPolicyType)) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCSP;
+ aLoadingPrincipal->GetPreloadCsp(getter_AddRefs(preloadCSP));
+ if (preloadCSP) {
+ preloadCSP->RequireSRIForType(externalType, &mEnforceSRI);
+ }
+ }
+ }
+ }
+
+ InheritOriginAttributes(mLoadingPrincipal, mOriginAttributes);
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (aLoadingContext) {
+ nsCOMPtr<nsILoadContext> loadContext =
+ aLoadingContext->OwnerDoc()->GetLoadContext();
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (loadContext && docShell &&
+ docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ bool usePrivateBrowsing;
+ nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+ if (NS_SUCCEEDED(rv)) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing);
+ }
+ }
+ }
+
+ // For chrome docshell, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the docshell is content.
+ if (aLoadingContext) {
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (docShell) {
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+ }
+ }
+}
+
+/* Constructor takes an outer window, but no loadingNode or loadingPrincipal.
+ * This constructor should only be used for TYPE_DOCUMENT loads, since they
+ * have a null loadingNode and loadingPrincipal.
+*/
+LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags)
+ : mLoadingPrincipal(nullptr)
+ , mTriggeringPrincipal(aTriggeringPrincipal)
+ , mPrincipalToInherit(nullptr)
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT)
+ , mTainting(LoadTainting::Basic)
+ , mUpgradeInsecureRequests(false)
+ , mVerifySignedContent(false)
+ , mEnforceSRI(false)
+ , mForceInheritPrincipalDropped(false)
+ , mInnerWindowID(0)
+ , mOuterWindowID(0)
+ , mParentOuterWindowID(0)
+ , mFrameOuterWindowID(0)
+ , mEnforceSecurity(false)
+ , mInitialSecurityCheckDone(false)
+ , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
+ , mForcePreflight(false)
+ , mIsPreflight(false)
+ , mForceHSTSPriming(false)
+ , mMixedContentWouldBlock(false)
+{
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aOuterWindow);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+ mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ mForceInheritPrincipalDropped = true;
+ }
+
+ // NB: Ignore the current inner window since we're navigating away from it.
+ mOuterWindowID = aOuterWindow->WindowID();
+
+ // TODO We can have a parent without a frame element in some cases dealing
+ // with the hidden window.
+ nsCOMPtr<nsPIDOMWindowOuter> parent = aOuterWindow->GetScriptableParent();
+ mParentOuterWindowID = parent ? parent->WindowID() : 0;
+
+ // get the docshell from the outerwindow, and then get the originattributes
+ nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
+ MOZ_ASSERT(docShell);
+ const DocShellOriginAttributes attrs =
+ nsDocShell::Cast(docShell)->GetOriginAttributes();
+
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ MOZ_ASSERT(attrs.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+
+ mOriginAttributes.InheritFromDocShellToNecko(attrs);
+}
+
+LoadInfo::LoadInfo(const LoadInfo& rhs)
+ : mLoadingPrincipal(rhs.mLoadingPrincipal)
+ , mTriggeringPrincipal(rhs.mTriggeringPrincipal)
+ , mPrincipalToInherit(rhs.mPrincipalToInherit)
+ , mLoadingContext(rhs.mLoadingContext)
+ , mSecurityFlags(rhs.mSecurityFlags)
+ , mInternalContentPolicyType(rhs.mInternalContentPolicyType)
+ , mTainting(rhs.mTainting)
+ , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests)
+ , mVerifySignedContent(rhs.mVerifySignedContent)
+ , mEnforceSRI(rhs.mEnforceSRI)
+ , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped)
+ , mInnerWindowID(rhs.mInnerWindowID)
+ , mOuterWindowID(rhs.mOuterWindowID)
+ , mParentOuterWindowID(rhs.mParentOuterWindowID)
+ , mFrameOuterWindowID(rhs.mFrameOuterWindowID)
+ , mEnforceSecurity(rhs.mEnforceSecurity)
+ , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone)
+ , mIsThirdPartyContext(rhs.mIsThirdPartyContext)
+ , mOriginAttributes(rhs.mOriginAttributes)
+ , mRedirectChainIncludingInternalRedirects(
+ rhs.mRedirectChainIncludingInternalRedirects)
+ , mRedirectChain(rhs.mRedirectChain)
+ , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
+ , mForcePreflight(rhs.mForcePreflight)
+ , mIsPreflight(rhs.mIsPreflight)
+ , mForceHSTSPriming(rhs.mForceHSTSPriming)
+ , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock)
+{
+}
+
+LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting,
+ bool aUpgradeInsecureRequests,
+ bool aVerifySignedContent,
+ bool aEnforceSRI,
+ bool aForceInheritPrincipalDropped,
+ uint64_t aInnerWindowID,
+ uint64_t aOuterWindowID,
+ uint64_t aParentOuterWindowID,
+ uint64_t aFrameOuterWindowID,
+ bool aEnforceSecurity,
+ bool aInitialSecurityCheckDone,
+ bool aIsThirdPartyContext,
+ const NeckoOriginAttributes& aOriginAttributes,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders,
+ bool aForcePreflight,
+ bool aIsPreflight,
+ bool aForceHSTSPriming,
+ bool aMixedContentWouldBlock)
+ : mLoadingPrincipal(aLoadingPrincipal)
+ , mTriggeringPrincipal(aTriggeringPrincipal)
+ , mPrincipalToInherit(aPrincipalToInherit)
+ , mSecurityFlags(aSecurityFlags)
+ , mInternalContentPolicyType(aContentPolicyType)
+ , mTainting(aTainting)
+ , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
+ , mVerifySignedContent(aVerifySignedContent)
+ , mEnforceSRI(aEnforceSRI)
+ , mForceInheritPrincipalDropped(aForceInheritPrincipalDropped)
+ , mInnerWindowID(aInnerWindowID)
+ , mOuterWindowID(aOuterWindowID)
+ , mParentOuterWindowID(aParentOuterWindowID)
+ , mFrameOuterWindowID(aFrameOuterWindowID)
+ , mEnforceSecurity(aEnforceSecurity)
+ , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
+ , mIsThirdPartyContext(aIsThirdPartyContext)
+ , mOriginAttributes(aOriginAttributes)
+ , mCorsUnsafeHeaders(aCorsUnsafeHeaders)
+ , mForcePreflight(aForcePreflight)
+ , mIsPreflight(aIsPreflight)
+ , mForceHSTSPriming (aForceHSTSPriming)
+ , mMixedContentWouldBlock(aMixedContentWouldBlock)
+{
+ // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
+ MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ mRedirectChainIncludingInternalRedirects.SwapElements(
+ aRedirectChainIncludingInternalRedirects);
+
+ mRedirectChain.SwapElements(aRedirectChain);
+}
+
+LoadInfo::~LoadInfo()
+{
+}
+
+void
+LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow)
+{
+ nsContentPolicyType type =
+ nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType);
+ if (type == nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ nsCOMPtr<mozIThirdPartyUtil> util(do_GetService(THIRDPARTYUTIL_CONTRACTID));
+ if (NS_WARN_IF(!util)) {
+ return;
+ }
+
+ util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext);
+}
+
+NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo)
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::Clone() const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::CloneWithNewSecFlags(nsSecurityFlags aSecurityFlags) const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mSecurityFlags = aSecurityFlags;
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo>
+LoadInfo::CloneForNewRequest() const
+{
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mEnforceSecurity = false;
+ copy->mInitialSecurityCheckDone = false;
+ copy->mRedirectChainIncludingInternalRedirects.Clear();
+ copy->mRedirectChain.Clear();
+ return copy.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal)
+{
+ NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal);
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::LoadingPrincipal()
+{
+ return mLoadingPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal)
+{
+ NS_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal);
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::TriggeringPrincipal()
+{
+ return mTriggeringPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit)
+{
+ NS_IF_ADDREF(*aPrincipalToInherit = mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit)
+{
+ MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit");
+ mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::PrincipalToInherit()
+{
+ return mPrincipalToInherit;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingDocument(nsIDOMDocument** aResult)
+{
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ if (node) {
+ nsCOMPtr<nsIDOMDocument> context = do_QueryInterface(node->OwnerDoc());
+ context.forget(aResult);
+ }
+ return NS_OK;
+}
+
+nsINode*
+LoadInfo::LoadingNode()
+{
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ return node;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult)
+{
+ *aResult = mSecurityFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityMode(uint32_t* aFlags)
+{
+ *aFlags = (mSecurityFlags &
+ (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext)
+{
+ *aIsInThirdPartyContext = mIsThirdPartyContext;
+ return NS_OK;
+}
+
+static const uint32_t sCookiePolicyMask =
+ nsILoadInfo::SEC_COOKIES_DEFAULT |
+ nsILoadInfo::SEC_COOKIES_INCLUDE |
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN |
+ nsILoadInfo::SEC_COOKIES_OMIT;
+
+NS_IMETHODIMP
+LoadInfo::GetCookiePolicy(uint32_t *aResult)
+{
+ uint32_t policy = mSecurityFlags & sCookiePolicyMask;
+ if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) {
+ policy = (mSecurityFlags & SEC_REQUIRE_CORS_DATA_INHERITS) ?
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN : nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ *aResult = policy;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetIncludeCookiesSecFlag()
+{
+ MOZ_ASSERT(!mEnforceSecurity,
+ "Request should not have been opened yet");
+ MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) ==
+ nsILoadInfo::SEC_COOKIES_DEFAULT);
+ mSecurityFlags = (mSecurityFlags & ~sCookiePolicyMask) |
+ nsILoadInfo::SEC_COOKIES_INCLUDE;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal)
+{
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal)
+{
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed)
+{
+ *aLoadingSandboxed = (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAboutBlankInherits(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowChrome(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDisallowScript(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+LoadInfo::GetDontFollowRedirects(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadErrorPage(bool* aResult)
+{
+ *aResult =
+ (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult)
+{
+ *aResult = nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType);
+ return NS_OK;
+}
+
+nsContentPolicyType
+LoadInfo::InternalContentPolicyType()
+{
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUpgradeInsecureRequests(bool* aResult)
+{
+ *aResult = mUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetVerifySignedContent(bool aVerifySignedContent)
+{
+ MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only verify content for TYPE_DOCUMENT");
+ mVerifySignedContent = aVerifySignedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetVerifySignedContent(bool* aResult)
+{
+ *aResult = mVerifySignedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetEnforceSRI(bool aEnforceSRI)
+{
+ mEnforceSRI = aEnforceSRI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetEnforceSRI(bool* aResult)
+{
+ *aResult = mEnforceSRI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalDropped(bool* aResult)
+{
+ *aResult = mForceInheritPrincipalDropped;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInnerWindowID(uint64_t* aResult)
+{
+ *aResult = mInnerWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetParentOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mParentOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameOuterWindowID(uint64_t* aResult)
+{
+ *aResult = mFrameOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::ResetPrincipalsToNullPrincipal()
+{
+ // take the originAttributes from the LoadInfo and create
+ // a new NullPrincipal using those origin attributes.
+ PrincipalOriginAttributes pAttrs;
+ pAttrs.InheritFromNecko(mOriginAttributes);
+ nsCOMPtr<nsIPrincipal> newNullPrincipal = nsNullPrincipal::Create(pAttrs);
+
+ MOZ_ASSERT(mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT ||
+ !mLoadingPrincipal,
+ "LoadingPrincipal should be null for toplevel loads");
+
+ // the loadingPrincipal for toplevel loads is always a nullptr;
+ if (mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) {
+ mLoadingPrincipal = newNullPrincipal;
+ }
+ mTriggeringPrincipal = newNullPrincipal;
+ mPrincipalToInherit = newNullPrincipal;
+
+ // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule
+ // any non null owner set on the channel and will return the principal
+ // form the loadinfo instead.
+ mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetScriptableOriginAttributes(JSContext* aCx,
+ JS::Handle<JS::Value> aOriginAttributes)
+{
+ NeckoOriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult
+LoadInfo::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes)
+{
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult
+LoadInfo::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes)
+{
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetEnforceSecurity(bool aEnforceSecurity)
+{
+ // Indicates whether the channel was openend using AsyncOpen2. Once set
+ // to true, it must remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aEnforceSecurity, "aEnforceSecurity must be true");
+ mEnforceSecurity = mEnforceSecurity || aEnforceSecurity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetEnforceSecurity(bool* aResult)
+{
+ *aResult = mEnforceSecurity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone)
+{
+ // Indicates whether the channel was ever evaluated by the
+ // ContentSecurityManager. Once set to true, this flag must
+ // remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aInitialSecurityCheckDone, "aInitialSecurityCheckDone must be true");
+ mInitialSecurityCheckDone = mInitialSecurityCheckDone || aInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInitialSecurityCheckDone(bool* aResult)
+{
+ *aResult = mInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::AppendRedirectedPrincipal(nsIPrincipal* aPrincipal, bool aIsInternalRedirect)
+{
+ NS_ENSURE_ARG(aPrincipal);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mRedirectChainIncludingInternalRedirects.AppendElement(aPrincipal);
+ if (!aIsInternalRedirect) {
+ mRedirectChain.AppendElement(aPrincipal);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChainIncludingInternalRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aChain)
+{
+ if (!ToJSValue(aCx, mRedirectChainIncludingInternalRedirects, aChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>&
+LoadInfo::RedirectChainIncludingInternalRedirects()
+{
+ return mRedirectChainIncludingInternalRedirects;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChain(JSContext* aCx, JS::MutableHandle<JS::Value> aChain)
+{
+ if (!ToJSValue(aCx, mRedirectChain, aChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>&
+LoadInfo::RedirectChain()
+{
+ return mRedirectChain;
+}
+
+void
+LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight)
+{
+ MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mCorsUnsafeHeaders = aHeaders;
+ mForcePreflight = aForcePreflight;
+}
+
+const nsTArray<nsCString>&
+LoadInfo::CorsUnsafeHeaders()
+{
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForcePreflight(bool* aForcePreflight)
+{
+ *aForcePreflight = mForcePreflight;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetIsPreflight()
+{
+ MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mIsPreflight = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsPreflight(bool* aIsPreflight)
+{
+ *aIsPreflight = mIsPreflight;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceHSTSPriming(bool* aForceHSTSPriming)
+{
+ *aForceHSTSPriming = mForceHSTSPriming;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetMixedContentWouldBlock(bool *aMixedContentWouldBlock)
+{
+ *aMixedContentWouldBlock = mMixedContentWouldBlock;
+ return NS_OK;
+}
+
+void
+LoadInfo::SetHSTSPriming(bool aMixedContentWouldBlock)
+{
+ mForceHSTSPriming = true;
+ mMixedContentWouldBlock = aMixedContentWouldBlock;
+}
+
+void
+LoadInfo::ClearHSTSPriming()
+{
+ mForceHSTSPriming = false;
+ mMixedContentWouldBlock = false;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTainting(uint32_t* aTaintingOut)
+{
+ MOZ_ASSERT(aTaintingOut);
+ *aTaintingOut = static_cast<uint32_t>(mTainting);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::MaybeIncreaseTainting(uint32_t aTainting)
+{
+ NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE);
+ LoadTainting tainting = static_cast<LoadTainting>(aTainting);
+ if (tainting > mTainting) {
+ mTainting = tainting;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsTopLevelLoad(bool *aResult)
+{
+ *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID
+ : mParentOuterWindowID == mOuterWindowID;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
new file mode 100644
index 000000000..261f85349
--- /dev/null
+++ b/netwerk/base/LoadInfo.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadInfo_h
+#define mozilla_LoadInfo_h
+
+#include "nsIContentPolicy.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsIURI.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasePrincipal.h"
+
+class nsINode;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+namespace dom {
+class XMLHttpRequestMainThread;
+}
+
+namespace net {
+class OptionalLoadInfoArgs;
+} // namespace net
+
+namespace ipc {
+// we have to forward declare that function so we can use it as a friend.
+nsresult
+LoadInfoArgsToLoadInfo(const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
+ nsILoadInfo** outLoadInfo);
+} // namespace ipc
+
+namespace net {
+
+/**
+ * Class that provides an nsILoadInfo implementation.
+ *
+ * Note that there is no reason why this class should be MOZ_EXPORT, but
+ * Thunderbird relies on some insane hacks which require this, so we'll leave it
+ * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword
+ * from this class at some point. See bug 1149127 for the discussion.
+ */
+class MOZ_EXPORT LoadInfo final : public nsILoadInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ // aLoadingPrincipal MUST NOT BE NULL.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType);
+
+ // Constructor used for TYPE_DOCUMENT loads which have no reasonable
+ // loadingNode or loadingPrincipal
+ LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags);
+
+ // create an exact copy of the loadinfo
+ already_AddRefed<nsILoadInfo> Clone() const;
+ // hands off!!! don't use CloneWithNewSecFlags unless you know
+ // exactly what you are doing - it should only be used within
+ // nsBaseChannel::Redirect()
+ already_AddRefed<nsILoadInfo>
+ CloneWithNewSecFlags(nsSecurityFlags aSecurityFlags) const;
+ // creates a copy of the loadinfo which is appropriate to use for a
+ // separate request. I.e. not for a redirect or an inner channel, but
+ // when a separate request is made with the same security properties.
+ already_AddRefed<nsILoadInfo> CloneForNewRequest() const;
+
+ void SetIsPreflight();
+
+private:
+ // private constructor that is only allowed to be called from within
+ // HttpChannelParent and FTPChannelParent declared as friends undeneath.
+ // In e10s we can not serialize nsINode, hence we store the innerWindowID.
+ // Please note that aRedirectChain uses swapElements.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting,
+ bool aUpgradeInsecureRequests,
+ bool aVerifySignedContent,
+ bool aEnforceSRI,
+ bool aForceInheritPrincipalDropped,
+ uint64_t aInnerWindowID,
+ uint64_t aOuterWindowID,
+ uint64_t aParentOuterWindowID,
+ uint64_t aFrameOuterWindowID,
+ bool aEnforceSecurity,
+ bool aInitialSecurityCheckDone,
+ bool aIsThirdPartyRequest,
+ const NeckoOriginAttributes& aOriginAttributes,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain,
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aForcePreflight,
+ bool aIsPreflight,
+ bool aForceHSTSPriming,
+ bool aMixedContentWouldBlock);
+ LoadInfo(const LoadInfo& rhs);
+
+ friend nsresult
+ mozilla::ipc::LoadInfoArgsToLoadInfo(
+ const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
+ nsILoadInfo** outLoadInfo);
+
+ ~LoadInfo();
+
+ void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
+
+ // This function is the *only* function which can change the securityflags
+ // of a loadinfo. It only exists because of the XHR code. Don't call it
+ // from anywhere else!
+ void SetIncludeCookiesSecFlag();
+ friend class mozilla::dom::XMLHttpRequestMainThread;
+
+ // if you add a member, please also update the copy constructor
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsWeakPtr mLoadingContext;
+ nsSecurityFlags mSecurityFlags;
+ nsContentPolicyType mInternalContentPolicyType;
+ LoadTainting mTainting;
+ bool mUpgradeInsecureRequests;
+ bool mVerifySignedContent;
+ bool mEnforceSRI;
+ bool mForceInheritPrincipalDropped;
+ uint64_t mInnerWindowID;
+ uint64_t mOuterWindowID;
+ uint64_t mParentOuterWindowID;
+ uint64_t mFrameOuterWindowID;
+ bool mEnforceSecurity;
+ bool mInitialSecurityCheckDone;
+ bool mIsThirdPartyContext;
+ NeckoOriginAttributes mOriginAttributes;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChainIncludingInternalRedirects;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChain;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ bool mForcePreflight;
+ bool mIsPreflight;
+
+ bool mForceHSTSPriming : 1;
+ bool mMixedContentWouldBlock : 1;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_LoadInfo_h
+
diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h
new file mode 100644
index 000000000..e2633969f
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+// https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+// * Basic: the request resulted in a same-origin or non-http load
+// * CORS: the request resulted in a cross-origin load with CORS headers
+// * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+//
+// NOTE: Checking the tainting is not currently adequate. You *must* still
+// check the final URL and CORS mode on the channel.
+//
+// These values are currently only set on the channel LoadInfo when the request
+// was initiated through fetch() or when a service worker interception occurs.
+// In the future we should set the tainting value within necko so that it is
+// consistently applied. Once that is done consumers can replace checks against
+// the final URL and CORS mode with checks against tainting.
+enum class LoadTainting : uint8_t
+{
+ Basic = 0,
+ CORS = 1,
+ Opaque = 2
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
new file mode 100644
index 000000000..f8add31aa
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryDownloader.h"
+
+#include "mozilla/Assertions.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(MemoryDownloader,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+MemoryDownloader::MemoryDownloader(IObserver* aObserver)
+: mObserver(aObserver)
+{
+}
+
+MemoryDownloader::~MemoryDownloader()
+{
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
+{
+ MOZ_ASSERT(!mData);
+ mData.reset(new FallibleTArray<uint8_t>());
+ mStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus)
+{
+ MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus));
+ MOZ_ASSERT(!mData == NS_FAILED(mStatus));
+ Data data;
+ data.swap(mData);
+ RefPtr<IObserver> observer;
+ observer.swap(mObserver);
+ observer->OnDownloadComplete(this, aRequest, aCtxt, aStatus,
+ mozilla::Move(data));
+ return NS_OK;
+}
+
+nsresult
+MemoryDownloader::ConsumeData(nsIInputStream* aIn,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ MemoryDownloader* self = static_cast<MemoryDownloader*>(aClosure);
+ if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) {
+ // The error returned by ConsumeData isn't propagated to the
+ // return of ReadSegments, so it has to be passed as state.
+ self->mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ uint32_t n;
+ MOZ_ASSERT(mData);
+ nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n);
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ mData.reset(nullptr);
+ return mStatus;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h
new file mode 100644
index 000000000..32fcff66c
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_MemoryDownloader_h__
+#define mozilla_net_MemoryDownloader_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+/**
+ * mozilla::net::MemoryDownloader
+ *
+ * This class is similar to nsIDownloader, but stores the downloaded
+ * stream in memory instead of a file. Ownership of the temporary
+ * memory is transferred to the observer when download is complete;
+ * there is no need to retain a reference to the downloader.
+ */
+
+namespace mozilla {
+namespace net {
+
+class MemoryDownloader final : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ typedef mozilla::UniquePtr<FallibleTArray<uint8_t>> Data;
+
+ class IObserver : public nsISupports {
+ public:
+ // Note: aData may be null if (and only if) aStatus indicates failure.
+ virtual void OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus,
+ Data aData) = 0;
+ };
+
+ explicit MemoryDownloader(IObserver* aObserver);
+
+private:
+ virtual ~MemoryDownloader();
+
+ static nsresult ConsumeData(nsIInputStream *in,
+ void *closure,
+ const char *fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount);
+
+ RefPtr<IObserver> mObserver;
+ Data mData;
+ nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_MemoryDownloader_h__
diff --git a/netwerk/base/NetStatistics.h b/netwerk/base/NetStatistics.h
new file mode 100644
index 000000000..261355083
--- /dev/null
+++ b/netwerk/base/NetStatistics.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetStatistics_h__
+#define NetStatistics_h__
+
+#include "mozilla/Assertions.h"
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsINetworkInterface.h"
+#include "nsINetworkManager.h"
+#include "nsINetworkStatsServiceProxy.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+// The following members are used for network per-app metering.
+const static uint64_t NETWORK_STATS_THRESHOLD = 65536;
+const static char NETWORK_STATS_NO_SERVICE_TYPE[] = "";
+
+inline nsresult
+GetActiveNetworkInfo(nsCOMPtr<nsINetworkInfo> &aNetworkInfo)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkManager> networkManager =
+ do_GetService("@mozilla.org/network/manager;1", &rv);
+
+ if (NS_FAILED(rv) || !networkManager) {
+ aNetworkInfo = nullptr;
+ return rv;
+ }
+
+ networkManager->GetActiveNetworkInfo(getter_AddRefs(aNetworkInfo));
+
+ return NS_OK;
+}
+
+class SaveNetworkStatsEvent : public Runnable {
+public:
+ SaveNetworkStatsEvent(uint32_t aAppId,
+ bool aIsInIsolatedMozBrowser,
+ nsMainThreadPtrHandle<nsINetworkInfo> &aActiveNetworkInfo,
+ uint64_t aCountRecv,
+ uint64_t aCountSent,
+ bool aIsAccumulative)
+ : mAppId(aAppId),
+ mIsInIsolatedMozBrowser(aIsInIsolatedMozBrowser),
+ mActiveNetworkInfo(aActiveNetworkInfo),
+ mCountRecv(aCountRecv),
+ mCountSent(aCountSent),
+ mIsAccumulative(aIsAccumulative)
+ {
+ MOZ_ASSERT(mAppId != NECKO_NO_APP_ID);
+ MOZ_ASSERT(mActiveNetworkInfo);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkStatsServiceProxy> mNetworkStatsServiceProxy =
+ do_GetService("@mozilla.org/networkstatsServiceProxy;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // save the network stats through NetworkStatsServiceProxy
+ mNetworkStatsServiceProxy->SaveAppStats(mAppId,
+ mIsInIsolatedMozBrowser,
+ mActiveNetworkInfo,
+ PR_Now() / 1000,
+ mCountRecv,
+ mCountSent,
+ mIsAccumulative,
+ nullptr);
+
+ return NS_OK;
+ }
+private:
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowser;
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+ uint64_t mCountRecv;
+ uint64_t mCountSent;
+ bool mIsAccumulative;
+};
+
+} // namespace mozilla:net
+} // namespace mozilla
+
+#endif // !NetStatistics_h__
diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm
new file mode 100644
index 000000000..e970c8ad8
--- /dev/null
+++ b/netwerk/base/NetUtil.jsm
@@ -0,0 +1,461 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+ * vim: sw=4 ts=4 sts=4 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [
+ "NetUtil",
+];
+
+/**
+ * Necko utilities
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const PR_UINT32_MAX = 0xffffffff;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// NetUtil Object
+
+this.NetUtil = {
+ /**
+ * Function to perform simple async copying from aSource (an input stream)
+ * to aSink (an output stream). The copy will happen on some background
+ * thread. Both streams will be closed when the copy completes.
+ *
+ * @param aSource
+ * The input stream to read from
+ * @param aSink
+ * The output stream to write to
+ * @param aCallback [optional]
+ * A function that will be called at copy completion with a single
+ * argument: the nsresult status code for the copy operation.
+ *
+ * @return An nsIRequest representing the copy operation (for example, this
+ * can be used to cancel the copying). The consumer can ignore the
+ * return value if desired.
+ */
+ asyncCopy: function NetUtil_asyncCopy(aSource, aSink,
+ aCallback = null)
+ {
+ if (!aSource || !aSink) {
+ let exception = new Components.Exception(
+ "Must have a source and a sink",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // make a stream copier
+ var copier = Cc["@mozilla.org/network/async-stream-copier;1"].
+ createInstance(Ci.nsIAsyncStreamCopier2);
+ copier.init(aSource, aSink,
+ null /* Default event target */,
+ 0 /* Default length */,
+ true, true /* Auto-close */);
+
+ var observer;
+ if (aCallback) {
+ observer = {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ aCallback(aStatusCode);
+ }
+ }
+ } else {
+ observer = null;
+ }
+
+ // start the copying
+ copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
+ return copier;
+ },
+
+ /**
+ * Asynchronously opens a source and fetches the response. While the fetch
+ * is asynchronous, I/O may happen on the main thread. When reading from
+ * a local file, prefer using "OS.File" methods instead.
+ *
+ * @param aSource
+ * This argument can be one of the following:
+ * - An options object that will be passed to NetUtil.newChannel.
+ * - An existing nsIChannel.
+ * - An existing nsIInputStream.
+ * Using an nsIURI, nsIFile, or string spec directly is deprecated.
+ * @param aCallback
+ * The callback function that will be notified upon completion. It
+ * will get these arguments:
+ * 1) An nsIInputStream containing the data from aSource, if any.
+ * 2) The status code from opening the source.
+ * 3) Reference to the nsIRequest.
+ */
+ asyncFetch: function NetUtil_asyncFetch(aSource, aCallback)
+ {
+ if (!aSource || !aCallback) {
+ let exception = new Components.Exception(
+ "Must have a source and a callback",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].
+ createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, PR_UINT32_MAX, null);
+
+ // Create a listener that will give data to the pipe's output stream.
+ let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
+ createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest: function(aRequest, aContext) {},
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ pipe.outputStream.close();
+ aCallback(pipe.inputStream, aStatusCode, aRequest);
+ }
+ });
+
+ // Input streams are handled slightly differently from everything else.
+ if (aSource instanceof Ci.nsIInputStream) {
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Ci.nsIInputStreamPump);
+ pump.init(aSource, -1, -1, 0, 0, true);
+ pump.asyncRead(listener, null);
+ return;
+ }
+
+ let channel = aSource;
+ if (!(channel instanceof Ci.nsIChannel)) {
+ channel = this.newChannel(aSource);
+ }
+
+ try {
+ // Open the channel using asyncOpen2() if the loadinfo contains one
+ // of the security mode flags, otherwise fall back to use asyncOpen().
+ if (channel.loadInfo &&
+ channel.loadInfo.securityMode != 0) {
+ channel.asyncOpen2(listener);
+ }
+ else {
+ // Log deprecation warning to console to make sure all channels
+ // are created providing the correct security flags in the loadinfo.
+ // See nsILoadInfo for all available security flags and also the API
+ // of NetUtil.newChannel() for details above.
+ Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " +
+ "one of the security flags set in the loadinfo (see nsILoadInfo). " +
+ "Please create channel using NetUtil.newChannel()");
+ channel.asyncOpen(listener, null);
+ }
+ }
+ catch (e) {
+ let exception = new Components.Exception(
+ "Failed to open input source '" + channel.originalURI.spec + "'",
+ e.result,
+ Components.stack.caller,
+ aSource,
+ e
+ );
+ throw exception;
+ }
+ },
+
+ /**
+ * Constructs a new URI for the given spec, character set, and base URI, or
+ * an nsIFile.
+ *
+ * @param aTarget
+ * The string spec for the desired URI or an nsIFile.
+ * @param aOriginCharset [optional]
+ * The character set for the URI. Only used if aTarget is not an
+ * nsIFile.
+ * @param aBaseURI [optional]
+ * The base URI for the spec. Only used if aTarget is not an
+ * nsIFile.
+ *
+ * @return an nsIURI object.
+ */
+ newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI)
+ {
+ if (!aTarget) {
+ let exception = new Components.Exception(
+ "Must have a non-null string spec or nsIFile object",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aTarget instanceof Ci.nsIFile) {
+ return this.ioService.newFileURI(aTarget);
+ }
+
+ return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI);
+ },
+
+ /**
+ * Constructs a new channel for the given source.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * @param aWhatToLoad
+ * This argument used to be a string spec for the desired URI, an
+ * nsIURI, or an nsIFile. Now it should be an options object with
+ * the following properties:
+ * {
+ * uri:
+ * The full URI spec string, nsIURI or nsIFile to create the
+ * channel for.
+ * Note that this cannot be an nsIFile if you have to specify a
+ * non-default charset or base URI. Call NetUtil.newURI first if
+ * you need to construct an URI using those options.
+ * loadingNode:
+ * loadingPrincipal:
+ * triggeringPrincipal:
+ * securityFlags:
+ * contentPolicyType:
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * loadUsingSystemPrincipal:
+ * Set this to true to use the system principal as
+ * loadingPrincipal. This must be omitted if loadingPrincipal or
+ * loadingNode are present.
+ * This should be used with care as it skips security checks.
+ * }
+ * @param aOriginCharset [deprecated]
+ * The character set for the URI. Only used if aWhatToLoad is a
+ * string, which is a deprecated API. Must be undefined otherwise.
+ * Use NetUtil.newURI if you need to use this option.
+ * @param aBaseURI [deprecated]
+ * The base URI for the spec. Only used if aWhatToLoad is a string,
+ * which is a deprecated API. Must be undefined otherwise. Use
+ * NetUtil.newURI if you need to use this option.
+ * @return an nsIChannel object.
+ */
+ newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI)
+ {
+ // Check for the deprecated API first.
+ if (typeof aWhatToLoad == "string" ||
+ (aWhatToLoad instanceof Ci.nsIFile) ||
+ (aWhatToLoad instanceof Ci.nsIURI)) {
+
+ let uri = (aWhatToLoad instanceof Ci.nsIURI)
+ ? aWhatToLoad
+ : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
+
+ // log deprecation warning for developers.
+ Services.console.logStringMessage(
+ "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'");
+
+ // Provide default loadinfo arguments and call the new API.
+ let systemPrincipal =
+ Services.scriptSecurityManager.getSystemPrincipal();
+
+ return this.ioService.newChannelFromURI2(
+ uri,
+ null, // loadingNode
+ systemPrincipal, // loadingPrincipal
+ null, // triggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ }
+
+ // We are using the updated API, that requires only the options object.
+ if (typeof aWhatToLoad != "object" ||
+ aOriginCharset !== undefined ||
+ aBaseURI !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires a single object argument",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ let { uri,
+ loadingNode,
+ loadingPrincipal,
+ loadUsingSystemPrincipal,
+ triggeringPrincipal,
+ securityFlags,
+ contentPolicyType } = aWhatToLoad;
+
+ if (!uri) {
+ throw new Components.Exception(
+ "newChannel requires the 'uri' property on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
+ uri = this.newURI(uri);
+ }
+
+ if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires at least one of the 'loadingNode'," +
+ " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
+ " properties on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (loadUsingSystemPrincipal === true) {
+ if (loadingNode || loadingPrincipal) {
+ throw new Components.Exception(
+ "newChannel does not accept 'loadUsingSystemPrincipal'" +
+ " if the 'loadingNode' or 'loadingPrincipal' properties" +
+ " are present on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ loadingPrincipal = Services.scriptSecurityManager
+ .getSystemPrincipal();
+ } else if (loadUsingSystemPrincipal !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires the 'loadUsingSystemPrincipal'" +
+ " property on the options object to be 'true' or 'undefined'.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (securityFlags === undefined) {
+ securityFlags = loadUsingSystemPrincipal
+ ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+ : Ci.nsILoadInfo.SEC_NORMAL;
+ }
+
+ if (contentPolicyType === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'contentPolicyType' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+ }
+
+ return this.ioService.newChannelFromURI2(uri,
+ loadingNode || null,
+ loadingPrincipal || null,
+ triggeringPrincipal || null,
+ securityFlags,
+ contentPolicyType);
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param aInputStream
+ * The input stream to read from.
+ * @param aCount
+ * The number of bytes to read from the stream.
+ * @param aOptions [optional]
+ * charset
+ * The character encoding of stream data.
+ * replacement
+ * The character to replace unknown byte sequences.
+ * If unset, it causes an exceptions to be thrown.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
+ */
+ readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream,
+ aCount,
+ aOptions)
+ {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ let exception = new Components.Exception(
+ "Non-zero amount of bytes must be specified",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aOptions && "charset" in aOptions) {
+ let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ try {
+ // When replacement is set, the character that is unknown sequence
+ // replaces with aOptions.replacement character.
+ if (!("replacement" in aOptions)) {
+ // aOptions.replacement isn't set.
+ // If input stream has unknown sequences for aOptions.charset,
+ // throw NS_ERROR_ILLEGAL_INPUT.
+ aOptions.replacement = 0;
+ }
+
+ cis.init(aInputStream, aOptions.charset, aCount,
+ aOptions.replacement);
+ let str = {};
+ cis.readString(-1, str);
+ cis.close();
+ return str.value;
+ }
+ catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(e.message, e.result,
+ Components.stack.caller, e.data);
+ }
+ }
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(aInputStream);
+ try {
+ return sis.readBytes(aCount);
+ }
+ catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(e.message, e.result,
+ Components.stack.caller, e.data);
+ }
+ },
+
+ /**
+ * Returns a reference to nsIIOService.
+ *
+ * @return a reference to nsIIOService.
+ */
+ get ioService()
+ {
+ delete this.ioService;
+ return this.ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ },
+};
diff --git a/netwerk/base/NetworkActivityMonitor.cpp b/netwerk/base/NetworkActivityMonitor.cpp
new file mode 100644
index 000000000..887878977
--- /dev/null
+++ b/netwerk/base/NetworkActivityMonitor.cpp
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkActivityMonitor.h"
+#include "prmem.h"
+#include "nsIObserverService.h"
+#include "nsPISocketTransportService.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "prerror.h"
+
+using namespace mozilla::net;
+
+static PRStatus
+nsNetMon_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+ PRStatus ret;
+ PRErrorCode code;
+ ret = fd->lower->methods->connect(fd->lower, addr, timeout);
+ if (ret == PR_SUCCESS || (code = PR_GetError()) == PR_WOULD_BLOCK_ERROR ||
+ code == PR_IN_PROGRESS_ERROR)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Read(PRFileDesc *fd, void *buf, int32_t len)
+{
+ int32_t ret;
+ ret = fd->lower->methods->read(fd->lower, buf, len);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Write(PRFileDesc *fd, const void *buf, int32_t len)
+{
+ int32_t ret;
+ ret = fd->lower->methods->write(fd->lower, buf, len);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Writev(PRFileDesc *fd,
+ const PRIOVec *iov,
+ int32_t size,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Recv(PRFileDesc *fd,
+ void *buf,
+ int32_t amount,
+ int flags,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_Send(PRFileDesc *fd,
+ const void *buf,
+ int32_t amount,
+ int flags,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_RecvFrom(PRFileDesc *fd,
+ void *buf,
+ int32_t amount,
+ int flags,
+ PRNetAddr *addr,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->recvfrom(fd->lower,
+ buf,
+ amount,
+ flags,
+ addr,
+ timeout);
+ if (ret >= 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_SendTo(PRFileDesc *fd,
+ const void *buf,
+ int32_t amount,
+ int flags,
+ const PRNetAddr *addr,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = fd->lower->methods->sendto(fd->lower,
+ buf,
+ amount,
+ flags,
+ addr,
+ timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+ return ret;
+}
+
+static int32_t
+nsNetMon_AcceptRead(PRFileDesc *listenSock,
+ PRFileDesc **acceptedSock,
+ PRNetAddr **peerAddr,
+ void *buf,
+ int32_t amount,
+ PRIntervalTime timeout)
+{
+ int32_t ret;
+ ret = listenSock->lower->methods->acceptread(listenSock->lower,
+ acceptedSock,
+ peerAddr,
+ buf,
+ amount,
+ timeout);
+ if (ret > 0)
+ NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+ return ret;
+}
+
+
+class NotifyNetworkActivity : public mozilla::Runnable {
+public:
+ explicit NotifyNetworkActivity(NetworkActivityMonitor::Direction aDirection)
+ : mDirection(aDirection)
+ {}
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs)
+ return NS_ERROR_FAILURE;
+
+ obs->NotifyObservers(nullptr,
+ mDirection == NetworkActivityMonitor::kUpload
+ ? NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC
+ : NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC,
+ nullptr);
+ return NS_OK;
+ }
+private:
+ NetworkActivityMonitor::Direction mDirection;
+};
+
+NetworkActivityMonitor * NetworkActivityMonitor::gInstance = nullptr;
+static PRDescIdentity sNetActivityMonitorLayerIdentity;
+static PRIOMethods sNetActivityMonitorLayerMethods;
+static PRIOMethods *sNetActivityMonitorLayerMethodsPtr = nullptr;
+
+NetworkActivityMonitor::NetworkActivityMonitor()
+ : mBlipInterval(PR_INTERVAL_NO_TIMEOUT)
+{
+ MOZ_COUNT_CTOR(NetworkActivityMonitor);
+
+ NS_ASSERTION(gInstance==nullptr,
+ "multiple NetworkActivityMonitor instances!");
+}
+
+NetworkActivityMonitor::~NetworkActivityMonitor()
+{
+ MOZ_COUNT_DTOR(NetworkActivityMonitor);
+ gInstance = nullptr;
+}
+
+nsresult
+NetworkActivityMonitor::Init(int32_t blipInterval)
+{
+ nsresult rv;
+
+ if (gInstance)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NetworkActivityMonitor * mon = new NetworkActivityMonitor();
+ rv = mon->Init_Internal(blipInterval);
+ if (NS_FAILED(rv)) {
+ delete mon;
+ return rv;
+ }
+
+ gInstance = mon;
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Shutdown()
+{
+ if (!gInstance)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ delete gInstance;
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Init_Internal(int32_t blipInterval)
+{
+ if (!sNetActivityMonitorLayerMethodsPtr) {
+ sNetActivityMonitorLayerIdentity =
+ PR_GetUniqueIdentity("network activity monitor layer");
+ sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
+ sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
+ sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
+ sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
+ sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
+ sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
+ sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
+ sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
+ sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
+ sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
+ sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
+ }
+
+ mBlipInterval = PR_MillisecondsToInterval(blipInterval);
+ // Set the last notification times to time that has just expired, so any
+ // activity even right now will trigger notification.
+ mLastNotificationTime[kUpload] = PR_IntervalNow() - mBlipInterval;
+ mLastNotificationTime[kDownload] = mLastNotificationTime[kUpload];
+
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::AttachIOLayer(PRFileDesc *fd)
+{
+ if (!gInstance)
+ return NS_OK;
+
+ PRFileDesc * layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_DELETE(layer);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::DataInOut(Direction direction)
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (gInstance) {
+ PRIntervalTime now = PR_IntervalNow();
+ if ((now - gInstance->mLastNotificationTime[direction]) >
+ gInstance->mBlipInterval) {
+ gInstance->mLastNotificationTime[direction] = now;
+ gInstance->PostNotification(direction);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+NetworkActivityMonitor::PostNotification(Direction direction)
+{
+ nsCOMPtr<nsIRunnable> ev = new NotifyNetworkActivity(direction);
+ NS_DispatchToMainThread(ev);
+}
diff --git a/netwerk/base/NetworkActivityMonitor.h b/netwerk/base/NetworkActivityMonitor.h
new file mode 100644
index 000000000..28c9a911e
--- /dev/null
+++ b/netwerk/base/NetworkActivityMonitor.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkActivityMonitor_h___
+#define NetworkActivityMonitor_h___
+
+#include <stdint.h>
+#include "nscore.h"
+#include "prio.h"
+#include "prinrval.h"
+
+namespace mozilla { namespace net {
+
+class NetworkActivityMonitor
+{
+public:
+ enum Direction {
+ kUpload = 0,
+ kDownload = 1
+ };
+
+ NetworkActivityMonitor();
+ ~NetworkActivityMonitor();
+
+ static nsresult Init(int32_t blipInterval);
+ static nsresult Shutdown();
+
+ static nsresult AttachIOLayer(PRFileDesc *fd);
+ static nsresult DataInOut(Direction direction);
+
+private:
+ nsresult Init_Internal(int32_t blipInterval);
+ void PostNotification(Direction direction);
+
+ static NetworkActivityMonitor * gInstance;
+ PRIntervalTime mBlipInterval;
+ PRIntervalTime mLastNotificationTime[2];
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NetworkActivityMonitor_h___ */
diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp
new file mode 100644
index 000000000..937c72658
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceCocoa.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap);
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] {
+ close(fd);
+ });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*) ((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap)
+{
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch(family=ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h
new file mode 100644
index 000000000..6f92c335f
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceImpl.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+typedef nsDataHashtable<nsCStringHashKey, nsCString> AddrMapType;
+
+nsresult DoListAddresses(AddrMapType& aAddrMap);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceLinux.cpp b/netwerk/base/NetworkInfoServiceLinux.cpp
new file mode 100644
index 000000000..96627cfec
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceLinux.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap);
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] {
+ close(fd);
+ });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = sizeof(struct ifreq);
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*) ((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap)
+{
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch(family=ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp
new file mode 100644
index 000000000..2a9448e35
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceWindows.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <iphlpapi.h>
+
+#include "mozilla/UniquePtr.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ UniquePtr<MIB_IPADDRTABLE> ipAddrTable;
+ DWORD size = sizeof(MIB_IPADDRTABLE);
+
+ ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ if (retVal == ERROR_INSUFFICIENT_BUFFER) {
+ ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+ retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ }
+ if (retVal != NO_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (DWORD i = 0; i < ipAddrTable->dwNumEntries; i++) {
+ int index = ipAddrTable->table[i].dwIndex;
+ uint32_t addrVal = (uint32_t) ipAddrTable->table[i].dwAddr;
+
+ nsCString indexString;
+ indexString.AppendInt(index, 10);
+
+ nsCString addrString;
+ addrString.AppendPrintf("%d.%d.%d.%d",
+ (addrVal >> 0) & 0xff, (addrVal >> 8) & 0xff,
+ (addrVal >> 16) & 0xff, (addrVal >> 24) & 0xff);
+
+ aAddrMap.Put(indexString, addrString);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp
new file mode 100644
index 000000000..9cb45efde
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,347 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+#include "ShutdownLayer.h"
+#else
+#include <fcntl.h>
+#define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods sPollableEventLayerMethods;
+static PRIOMethods *sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (sPollableEventLayerMethodsPtr) {
+ return;
+ }
+ sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
+ sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
+ sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
+}
+
+static bool NewTCPSocketPair(PRFileDesc *fd[], bool aSetRecvBuff)
+{
+ // this is a replacement for PR_NewTCPSocketPair that manually
+ // sets the recv buffer to 64K. A windows bug (1248358)
+ // can result in using an incompatible rwin and window
+ // scale option on localhost pipes if not set before connect.
+
+ SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", aSetRecvBuff ? "with" : "without"));
+
+ PRFileDesc *listener = nullptr;
+ PRFileDesc *writer = nullptr;
+ PRFileDesc *reader = nullptr;
+ PRSocketOptionData recvBufferOpt;
+ recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
+ recvBufferOpt.value.recv_buffer_size = 65535;
+
+ PRSocketOptionData nodelayOpt;
+ nodelayOpt.option = PR_SockOpt_NoDelay;
+ nodelayOpt.value.no_delay = true;
+
+ PRSocketOptionData noblockOpt;
+ noblockOpt.option = PR_SockOpt_Nonblocking;
+ noblockOpt.value.non_blocking = true;
+
+ listener = PR_OpenTCPSocket(PR_AF_INET);
+ if (!listener) {
+ goto failed;
+ }
+
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(listener, &recvBufferOpt);
+ }
+ PR_SetSocketOption(listener, &nodelayOpt);
+
+ PRNetAddr listenAddr;
+ memset(&listenAddr, 0, sizeof(listenAddr));
+ if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
+ (PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
+ (PR_GetSockName(listener, &listenAddr) == PR_FAILURE) || // learn the dynamic port
+ (PR_Listen(listener, 5) == PR_FAILURE)) {
+ goto failed;
+ }
+
+ writer = PR_OpenTCPSocket(PR_AF_INET);
+ if (!writer) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(writer, &recvBufferOpt);
+ }
+ PR_SetSocketOption(writer, &nodelayOpt);
+ PR_SetSocketOption(writer, &noblockOpt);
+ PRNetAddr writerAddr;
+ if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), &writerAddr) == PR_FAILURE) {
+ goto failed;
+ }
+
+ if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
+ if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
+ (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
+ goto failed;
+ }
+ }
+
+ reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
+ if (!reader) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(reader, &recvBufferOpt);
+ }
+ PR_SetSocketOption(reader, &nodelayOpt);
+ PR_SetSocketOption(reader, &noblockOpt);
+ PR_Close(listener);
+
+ fd[0] = reader;
+ fd[1] = writer;
+ return true;
+
+failed:
+ if (listener) {
+ PR_Close(listener);
+ }
+ if (reader) {
+ PR_Close(reader);
+ }
+ if (writer) {
+ PR_Close(writer);
+ }
+ return false;
+}
+
+#endif
+
+PollableEvent::PollableEvent()
+ : mWriteFD(nullptr)
+ , mReadFD(nullptr)
+ , mSignaled(false)
+{
+ MOZ_COUNT_CTOR(PollableEvent);
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // create pair of prfiledesc that can be used as a poll()ble
+ // signal. on windows use a localhost socket pair, and on
+ // unix use a pipe.
+#ifdef USEPIPE
+ SOCKET_LOG(("PollableEvent() using pipe\n"));
+ if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
+ // make the pipe non blocking. NSPR asserts at
+ // trying to use SockOpt here
+ PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
+ int flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ fd = PR_FileDesc2NativeHandle(mWriteFD);
+ flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ } else {
+ mReadFD = nullptr;
+ mWriteFD = nullptr;
+ SOCKET_LOG(("PollableEvent() pipe failed\n"));
+ }
+#else
+ SOCKET_LOG(("PollableEvent() using socket pair\n"));
+ PRFileDesc *fd[2];
+ LazyInitSocket();
+
+ // Try with a increased recv buffer first (bug 1248358).
+ if (NewTCPSocketPair(fd, true)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If the previous fails try without recv buffer increase (bug 1305436).
+ } else if (NewTCPSocketPair(fd, false)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If both fail, try the old version.
+ } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+
+ PRSocketOptionData socket_opt;
+ DebugOnly<PRStatus> status;
+ socket_opt.option = PR_SockOpt_NoDelay;
+ socket_opt.value.no_delay = true;
+ PR_SetSocketOption(mWriteFD, &socket_opt);
+ PR_SetSocketOption(mReadFD, &socket_opt);
+ socket_opt.option = PR_SockOpt_Nonblocking;
+ socket_opt.value.non_blocking = true;
+ status = PR_SetSocketOption(mWriteFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ status = PR_SetSocketOption(mReadFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ }
+
+ if (mReadFD && mWriteFD) {
+ // compatibility with LSPs such as McAfee that assume a NSPR
+ // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop.
+ PRFileDesc *topLayer =
+ PR_CreateIOLayerStub(sPollableEventLayerIdentity,
+ sPollableEventLayerMethodsPtr);
+ if (topLayer) {
+ if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
+ topLayer->dtor(topLayer);
+ } else {
+ SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
+ mReadFD = topLayer;
+ }
+ }
+
+ } else {
+ SOCKET_LOG(("PollableEvent() socketpair failed\n"));
+ }
+#endif
+
+ if (mReadFD && mWriteFD) {
+ // prime the system to deal with races invovled in [dc]tor cycle
+ SOCKET_LOG(("PollableEvent() ctor ok\n"));
+ mSignaled = true;
+ PR_Write(mWriteFD, "I", 1);
+ }
+}
+
+PollableEvent::~PollableEvent()
+{
+ MOZ_COUNT_DTOR(PollableEvent);
+ if (mWriteFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mWriteFD);
+#endif
+ PR_Close(mWriteFD);
+ }
+ if (mReadFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mReadFD);
+#endif
+ PR_Close(mReadFD);
+ }
+}
+
+// we do not record signals on the socket thread
+// because the socket thread can reliably look at its
+// own runnable queue before selecting a poll time
+// this is the "service the network without blocking" comment in
+// nsSocketTransportService2.cpp
+bool
+PollableEvent::Signal()
+{
+ SOCKET_LOG(("PollableEvent::Signal\n"));
+
+ if (!mWriteFD) {
+ SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
+ return false;
+ }
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event. See bug 1292181.
+ if (PR_GetCurrentThread() == gSocketThread) {
+ SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
+ return true;
+ }
+#endif
+
+#ifndef XP_WIN
+ // To wake up the poll writing once is enough, but for Windows that can cause
+ // hangs so we will write for every event.
+ // For non-Windows systems it is enough to write just once.
+ if (mSignaled) {
+ return true;
+ }
+#endif
+
+ mSignaled = true;
+ int32_t status = PR_Write(mWriteFD, "M", 1);
+ SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
+ if (status != 1) {
+ NS_WARNING("PollableEvent::Signal Failed\n");
+ SOCKET_LOG(("PollableEvent::Signal Failed\n"));
+ mSignaled = false;
+ }
+ return (status == 1);
+}
+
+bool
+PollableEvent::Clear()
+{
+ // necessary because of the "dont signal on socket thread" optimization
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ SOCKET_LOG(("PollableEvent::Clear\n"));
+ mSignaled = false;
+ if (!mReadFD) {
+ SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
+ return false;
+ }
+ char buf[2048];
+ int32_t status;
+#ifdef XP_WIN
+ // On Windows we are writing to the socket for each event, to be sure that we
+ // do not have any deadlock read from the socket as much as we can.
+ while (true) {
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ } else {
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+ }
+ }
+ }
+#else
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
+
+ if (status == 1) {
+ return true;
+ }
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status > 1) {
+ MOZ_ASSERT(false);
+ SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
+ Clear();
+ return true;
+ }
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ }
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+#endif //XP_WIN
+
+}
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h
new file mode 100644
index 000000000..800079932
--- /dev/null
+++ b/netwerk/base/PollableEvent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PollableEvent_h__
+#define PollableEvent_h__
+
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+// class must be called locked
+class PollableEvent
+{
+public:
+ PollableEvent();
+ ~PollableEvent();
+
+ // Signal/Clear return false only if they fail
+ bool Signal();
+ bool Clear();
+ bool Valid() { return mWriteFD && mReadFD; }
+
+ PRFileDesc *PollableFD() { return mReadFD; }
+
+private:
+ PRFileDesc *mWriteFD;
+ PRFileDesc *mReadFD;
+ bool mSignaled;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp
new file mode 100644
index 000000000..e97b11d16
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2550 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "Predictor.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsICachingChannel.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsIDNSService.h"
+#include "nsIDocument.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsIIOService.h"
+#include "nsILoadContext.h"
+#include "nsILoadContextInfo.h"
+#include "nsILoadGroup.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+Predictor *Predictor::sSelf = nullptr;
+
+static LazyLogModule gPredictorLog("NetworkPredictor");
+
+#define PREDICTOR_LOG(args) MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
+
+#define RETURN_IF_FAILED(_rv) \
+ do { \
+ if (NS_FAILED(_rv)) { \
+ return; \
+ } \
+ } while (0)
+
+#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
+
+
+static const char PREDICTOR_ENABLED_PREF[] = "network.predictor.enabled";
+static const char PREDICTOR_SSL_HOVER_PREF[] = "network.predictor.enable-hover-on-ssl";
+static const char PREDICTOR_PREFETCH_PREF[] = "network.predictor.enable-prefetch";
+
+static const char PREDICTOR_PAGE_DELTA_DAY_PREF[] =
+ "network.predictor.page-degradation.day";
+static const int32_t PREDICTOR_PAGE_DELTA_DAY_DEFAULT = 0;
+static const char PREDICTOR_PAGE_DELTA_WEEK_PREF[] =
+ "network.predictor.page-degradation.week";
+static const int32_t PREDICTOR_PAGE_DELTA_WEEK_DEFAULT = 5;
+static const char PREDICTOR_PAGE_DELTA_MONTH_PREF[] =
+ "network.predictor.page-degradation.month";
+static const int32_t PREDICTOR_PAGE_DELTA_MONTH_DEFAULT = 10;
+static const char PREDICTOR_PAGE_DELTA_YEAR_PREF[] =
+ "network.predictor.page-degradation.year";
+static const int32_t PREDICTOR_PAGE_DELTA_YEAR_DEFAULT = 25;
+static const char PREDICTOR_PAGE_DELTA_MAX_PREF[] =
+ "network.predictor.page-degradation.max";
+static const int32_t PREDICTOR_PAGE_DELTA_MAX_DEFAULT = 50;
+static const char PREDICTOR_SUB_DELTA_DAY_PREF[] =
+ "network.predictor.subresource-degradation.day";
+static const int32_t PREDICTOR_SUB_DELTA_DAY_DEFAULT = 1;
+static const char PREDICTOR_SUB_DELTA_WEEK_PREF[] =
+ "network.predictor.subresource-degradation.week";
+static const int32_t PREDICTOR_SUB_DELTA_WEEK_DEFAULT = 10;
+static const char PREDICTOR_SUB_DELTA_MONTH_PREF[] =
+ "network.predictor.subresource-degradation.month";
+static const int32_t PREDICTOR_SUB_DELTA_MONTH_DEFAULT = 25;
+static const char PREDICTOR_SUB_DELTA_YEAR_PREF[] =
+ "network.predictor.subresource-degradation.year";
+static const int32_t PREDICTOR_SUB_DELTA_YEAR_DEFAULT = 50;
+static const char PREDICTOR_SUB_DELTA_MAX_PREF[] =
+ "network.predictor.subresource-degradation.max";
+static const int32_t PREDICTOR_SUB_DELTA_MAX_DEFAULT = 100;
+
+static const char PREDICTOR_PREFETCH_ROLLING_LOAD_PREF[] =
+ "network.predictor.prefetch-rolling-load-count";
+static const int32_t PREFETCH_ROLLING_LOAD_DEFAULT = 10;
+static const char PREDICTOR_PREFETCH_MIN_PREF[] =
+ "network.predictor.prefetch-min-confidence";
+static const int32_t PREFETCH_MIN_DEFAULT = 100;
+static const char PREDICTOR_PRECONNECT_MIN_PREF[] =
+ "network.predictor.preconnect-min-confidence";
+static const int32_t PRECONNECT_MIN_DEFAULT = 90;
+static const char PREDICTOR_PRERESOLVE_MIN_PREF[] =
+ "network.predictor.preresolve-min-confidence";
+static const int32_t PRERESOLVE_MIN_DEFAULT = 60;
+static const char PREDICTOR_REDIRECT_LIKELY_PREF[] =
+ "network.predictor.redirect-likely-confidence";
+static const int32_t REDIRECT_LIKELY_DEFAULT = 75;
+
+static const char PREDICTOR_PREFETCH_FORCE_VALID_PREF[] =
+ "network.predictor.prefetch-force-valid-for";
+static const int32_t PREFETCH_FORCE_VALID_DEFAULT = 10;
+
+static const char PREDICTOR_MAX_RESOURCES_PREF[] =
+ "network.predictor.max-resources-per-entry";
+static const uint32_t PREDICTOR_MAX_RESOURCES_DEFAULT = 100;
+
+// This is selected in concert with max-resources-per-entry to keep memory usage
+// low-ish. The default of the combo of the two is ~50k
+static const char PREDICTOR_MAX_URI_LENGTH_PREF[] =
+ "network.predictor.max-uri-length";
+static const uint32_t PREDICTOR_MAX_URI_LENGTH_DEFAULT = 500;
+
+static const char PREDICTOR_DOING_TESTS_PREF[] = "network.predictor.doing-tests";
+
+static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up";
+
+// All these time values are in sec
+static const uint32_t ONE_DAY = 86400U;
+static const uint32_t ONE_WEEK = 7U * ONE_DAY;
+static const uint32_t ONE_MONTH = 30U * ONE_DAY;
+static const uint32_t ONE_YEAR = 365U * ONE_DAY;
+
+static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
+
+// Version of metadata entries we expect
+static const uint32_t METADATA_VERSION = 1;
+
+// Flags available in entries
+// FLAG_PREFETCHABLE - we have determined that this item is eligible for prefetch
+static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
+
+// We save 12 bits in the "flags" section of our metadata for actual flags, the
+// rest are to keep track of a rolling count of which loads a resource has been
+// used on to determine if we can prefetch that resource or not;
+static const uint8_t kRollingLoadOffset = 12;
+static const int32_t kMaxPrefetchRollingLoadCount = 20;
+static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
+
+// ID Extensions for cache entries
+#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static nsresult
+ExtractOrigin(nsIURI *uri, nsIURI **originUri, nsIIOService *ioService)
+{
+ nsAutoCString s;
+ s.Truncate();
+ nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(originUri, s, nullptr, nullptr, ioService);
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool
+IsNullOrHttp(nsIURI *uri)
+{
+ if (!uri) {
+ return true;
+ }
+
+ bool isHTTP = false;
+ uri->SchemeIs("http", &isHTTP);
+ if (!isHTTP) {
+ uri->SchemeIs("https", &isHTTP);
+ }
+
+ return isHTTP;
+}
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the
+// Predictor can use.
+
+NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ return NS_OK;
+}
+
+// Class to proxy important information from the initial predictor call through
+// the cache API and back into the internals of the predictor. We can't use the
+// predictor itself, as it may have multiple actions in-flight, and each action
+// has different parameters.
+NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
+
+Predictor::Action::Action(bool fullUri, bool predict,
+ Predictor::Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier,
+ Predictor *predictor)
+ :mFullUri(fullUri)
+ ,mPredict(predict)
+ ,mTargetURI(targetURI)
+ ,mSourceURI(sourceURI)
+ ,mVerifier(verifier)
+ ,mStackCount(0)
+ ,mPredictor(predictor)
+{
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::Action(bool fullUri, bool predict,
+ Predictor::Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier,
+ Predictor *predictor, uint8_t stackCount)
+ :mFullUri(fullUri)
+ ,mPredict(predict)
+ ,mTargetURI(targetURI)
+ ,mSourceURI(sourceURI)
+ ,mVerifier(verifier)
+ ,mStackCount(stackCount)
+ ,mPredictor(predictor)
+{
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::~Action()
+{ }
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
+
+ nsAutoCString targetURI, sourceURI;
+ mTargetURI->GetAsciiSpec(targetURI);
+ if (mSourceURI) {
+ mSourceURI->GetAsciiSpec(sourceURI);
+ }
+ PREDICTOR_LOG(("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
+ "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
+ "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08x",
+ this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
+ targetURI.get(), sourceURI.get(), mStackCount,
+ isNew, result));
+ if (NS_FAILED(result)) {
+ PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08X). "
+ "Aborting.", this, result));
+ return NS_OK;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME,
+ mStartTime);
+ if (mPredict) {
+ bool predicted = mPredictor->PredictInternal(mPredictReason, entry, isNew,
+ mFullUri, mTargetURI,
+ mVerifier, mStackCount);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_WORK_TIME, mStartTime);
+ if (predicted) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
+ }
+ } else {
+ mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
+ mSourceURI);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_LEARN_WORK_TIME, mStartTime);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor,
+ nsINetworkPredictor,
+ nsIObserver,
+ nsISpeculativeConnectionOverrider,
+ nsIInterfaceRequestor,
+ nsICacheEntryMetaDataVisitor,
+ nsINetworkPredictorVerifier)
+
+Predictor::Predictor()
+ :mInitialized(false)
+ ,mEnabled(true)
+ ,mEnableHoverOnSSL(false)
+ ,mEnablePrefetch(true)
+ ,mPageDegradationDay(PREDICTOR_PAGE_DELTA_DAY_DEFAULT)
+ ,mPageDegradationWeek(PREDICTOR_PAGE_DELTA_WEEK_DEFAULT)
+ ,mPageDegradationMonth(PREDICTOR_PAGE_DELTA_MONTH_DEFAULT)
+ ,mPageDegradationYear(PREDICTOR_PAGE_DELTA_YEAR_DEFAULT)
+ ,mPageDegradationMax(PREDICTOR_PAGE_DELTA_MAX_DEFAULT)
+ ,mSubresourceDegradationDay(PREDICTOR_SUB_DELTA_DAY_DEFAULT)
+ ,mSubresourceDegradationWeek(PREDICTOR_SUB_DELTA_WEEK_DEFAULT)
+ ,mSubresourceDegradationMonth(PREDICTOR_SUB_DELTA_MONTH_DEFAULT)
+ ,mSubresourceDegradationYear(PREDICTOR_SUB_DELTA_YEAR_DEFAULT)
+ ,mSubresourceDegradationMax(PREDICTOR_SUB_DELTA_MAX_DEFAULT)
+ ,mPrefetchRollingLoadCount(PREFETCH_ROLLING_LOAD_DEFAULT)
+ ,mPrefetchMinConfidence(PREFETCH_MIN_DEFAULT)
+ ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT)
+ ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT)
+ ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT)
+ ,mPrefetchForceValidFor(PREFETCH_FORCE_VALID_DEFAULT)
+ ,mMaxResourcesPerEntry(PREDICTOR_MAX_RESOURCES_DEFAULT)
+ ,mStartupCount(1)
+ ,mMaxURILength(PREDICTOR_MAX_URI_LENGTH_DEFAULT)
+ ,mDoingTests(false)
+{
+ MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
+ sSelf = this;
+}
+
+Predictor::~Predictor()
+{
+ if (mInitialized)
+ Shutdown();
+
+ sSelf = nullptr;
+}
+
+// Predictor::nsIObserver
+
+nsresult
+Predictor::InstallObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::AddBoolVarCache(&mEnabled, PREDICTOR_ENABLED_PREF, true);
+ Preferences::AddBoolVarCache(&mEnableHoverOnSSL,
+ PREDICTOR_SSL_HOVER_PREF, false);
+ Preferences::AddBoolVarCache(&mEnablePrefetch, PREDICTOR_PREFETCH_PREF, true);
+ Preferences::AddIntVarCache(&mPageDegradationDay,
+ PREDICTOR_PAGE_DELTA_DAY_PREF,
+ PREDICTOR_PAGE_DELTA_DAY_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationWeek,
+ PREDICTOR_PAGE_DELTA_WEEK_PREF,
+ PREDICTOR_PAGE_DELTA_WEEK_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationMonth,
+ PREDICTOR_PAGE_DELTA_MONTH_PREF,
+ PREDICTOR_PAGE_DELTA_MONTH_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationYear,
+ PREDICTOR_PAGE_DELTA_YEAR_PREF,
+ PREDICTOR_PAGE_DELTA_YEAR_DEFAULT);
+ Preferences::AddIntVarCache(&mPageDegradationMax,
+ PREDICTOR_PAGE_DELTA_MAX_PREF,
+ PREDICTOR_PAGE_DELTA_MAX_DEFAULT);
+
+ Preferences::AddIntVarCache(&mSubresourceDegradationDay,
+ PREDICTOR_SUB_DELTA_DAY_PREF,
+ PREDICTOR_SUB_DELTA_DAY_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationWeek,
+ PREDICTOR_SUB_DELTA_WEEK_PREF,
+ PREDICTOR_SUB_DELTA_WEEK_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationMonth,
+ PREDICTOR_SUB_DELTA_MONTH_PREF,
+ PREDICTOR_SUB_DELTA_MONTH_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationYear,
+ PREDICTOR_SUB_DELTA_YEAR_PREF,
+ PREDICTOR_SUB_DELTA_YEAR_DEFAULT);
+ Preferences::AddIntVarCache(&mSubresourceDegradationMax,
+ PREDICTOR_SUB_DELTA_MAX_PREF,
+ PREDICTOR_SUB_DELTA_MAX_DEFAULT);
+
+ Preferences::AddIntVarCache(&mPrefetchRollingLoadCount,
+ PREDICTOR_PREFETCH_ROLLING_LOAD_PREF,
+ PREFETCH_ROLLING_LOAD_DEFAULT);
+ Preferences::AddIntVarCache(&mPrefetchMinConfidence,
+ PREDICTOR_PREFETCH_MIN_PREF,
+ PREFETCH_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mPreconnectMinConfidence,
+ PREDICTOR_PRECONNECT_MIN_PREF,
+ PRECONNECT_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mPreresolveMinConfidence,
+ PREDICTOR_PRERESOLVE_MIN_PREF,
+ PRERESOLVE_MIN_DEFAULT);
+ Preferences::AddIntVarCache(&mRedirectLikelyConfidence,
+ PREDICTOR_REDIRECT_LIKELY_PREF,
+ REDIRECT_LIKELY_DEFAULT);
+
+ Preferences::AddIntVarCache(&mPrefetchForceValidFor,
+ PREDICTOR_PREFETCH_FORCE_VALID_PREF,
+ PREFETCH_FORCE_VALID_DEFAULT);
+
+ Preferences::AddIntVarCache(&mMaxResourcesPerEntry,
+ PREDICTOR_MAX_RESOURCES_PREF,
+ PREDICTOR_MAX_RESOURCES_DEFAULT);
+
+ Preferences::AddBoolVarCache(&mCleanedUp, PREDICTOR_CLEANED_UP_PREF, false);
+
+ Preferences::AddUintVarCache(&mMaxURILength, PREDICTOR_MAX_URI_LENGTH_PREF,
+ PREDICTOR_MAX_URI_LENGTH_DEFAULT);
+
+ Preferences::AddBoolVarCache(&mDoingTests, PREDICTOR_DOING_TESTS_PREF, false);
+
+ if (!mCleanedUp) {
+ mCleanupTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mCleanupTimer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return rv;
+}
+
+void
+Predictor::RemoveObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
+
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ if (mCleanupTimer) {
+ mCleanupTimer->Cancel();
+ mCleanupTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data_unicode)
+{
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor observing something off main thread!");
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ } else if (!strcmp("timer-callback", topic)) {
+ MaybeCleanupOldDBFiles();
+ mCleanupTimer = nullptr;
+ }
+
+ return rv;
+}
+
+// Predictor::nsISpeculativeConnectionOverrider
+
+NS_IMETHODIMP
+Predictor::GetIgnoreIdle(bool *ignoreIdle)
+{
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetParallelSpeculativeConnectLimit(
+ uint32_t *parallelSpeculativeConnectLimit)
+{
+ *parallelSpeculativeConnectLimit = 6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetIsFromPredictor(bool *isFromPredictor)
+{
+ *isFromPredictor = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetAllow1918(bool *allow1918)
+{
+ *allow1918 = false;
+ return NS_OK;
+}
+
+// Predictor::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Predictor::GetInterface(const nsIID &iid, void **result)
+{
+ return QueryInterface(iid, result);
+}
+
+// Predictor::nsICacheEntryMetaDataVisitor
+
+#define SEEN_META_DATA "predictor::seen"
+#define RESOURCE_META_DATA "predictor::resource-count"
+#define META_DATA_PREFIX "predictor::"
+
+static bool
+IsURIMetadataElement(const char *key)
+{
+ return StringBeginsWith(nsDependentCString(key),
+ NS_LITERAL_CSTRING(META_DATA_PREFIX)) &&
+ !NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) &&
+ !NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key);
+}
+
+nsresult
+Predictor::OnMetaDataElement(const char *asciiKey, const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToOperateOn.AppendElement(key);
+ mValuesToOperateOn.AppendElement(value);
+
+ return NS_OK;
+}
+
+// Predictor::nsINetworkPredictor
+
+nsresult
+Predictor::Init()
+{
+ MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ rv = InstallObserver();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
+
+ if (!mDNSListener) {
+ mDNSListener = new DNSListener();
+ }
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContextInfo> lci =
+ new LoadContextInfo(false, NeckoOriginAttributes());
+
+ rv = cacheStorageService->DiskCacheStorage(lci, false,
+ getter_AddRefs(mCacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIOService = do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewURI(getter_AddRefs(mStartupURI),
+ "predictor://startup", nullptr, mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpeculativeService = do_QueryInterface(mIOService, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ return rv;
+}
+
+namespace {
+class PredictorThreadShutdownRunner : public Runnable
+{
+public:
+ PredictorThreadShutdownRunner(nsIThread *ioThread, bool success)
+ :mIOThread(ioThread)
+ ,mSuccess(success)
+ { }
+ ~PredictorThreadShutdownRunner() { }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!");
+ if (mSuccess) {
+ // This means the cleanup happened. Mark so we don't try in the
+ // future.
+ Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true);
+ }
+ return mIOThread->AsyncShutdown();
+ }
+
+private:
+ nsCOMPtr<nsIThread> mIOThread;
+ bool mSuccess;
+};
+
+class PredictorOldCleanupRunner : public Runnable
+{
+public:
+ PredictorOldCleanupRunner(nsIThread *ioThread, nsIFile *dbFile)
+ :mIOThread(ioThread)
+ ,mDBFile(dbFile)
+ { }
+
+ ~PredictorOldCleanupRunner() { }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!");
+ nsresult rv = CheckForAndDeleteOldDBFiles();
+ RefPtr<PredictorThreadShutdownRunner> runner =
+ new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv));
+ NS_DispatchToMainThread(runner);
+ return NS_OK;
+ }
+
+private:
+ nsresult CheckForAndDeleteOldDBFiles()
+ {
+ nsCOMPtr<nsIFile> oldDBFile;
+ nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool fileExists = false;
+ rv = oldDBFile->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileExists) {
+ rv = oldDBFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ fileExists = false;
+ rv = mDBFile->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fileExists) {
+ rv = mDBFile->Remove(false);
+ }
+
+ return rv;
+ }
+
+ nsCOMPtr<nsIThread> mIOThread;
+ nsCOMPtr<nsIFile> mDBFile;
+};
+
+} // namespace
+
+void
+Predictor::MaybeCleanupOldDBFiles()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mEnabled || mCleanedUp) {
+ return;
+ }
+
+ mCleanedUp = true;
+
+ // This is used for cleaning up junk left over from the old backend
+ // built on top of sqlite, if necessary.
+ nsCOMPtr<nsIFile> dbFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ RETURN_IF_FAILED(rv);
+ rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
+ RETURN_IF_FAILED(rv);
+
+ nsCOMPtr<nsIThread> ioThread;
+ rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread));
+ RETURN_IF_FAILED(rv);
+
+ RefPtr<PredictorOldCleanupRunner> runner =
+ new PredictorOldCleanupRunner(ioThread, dbFile);
+ ioThread->Dispatch(runner, NS_DISPATCH_NORMAL);
+}
+
+void
+Predictor::Shutdown()
+{
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
+ return;
+ }
+
+ RemoveObserver();
+
+ mInitialized = false;
+}
+
+nsresult
+Predictor::Create(nsISupports *aOuter, const nsIID& aIID,
+ void **aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<Predictor> svc = new Predictor();
+ if (IsNeckoChild()) {
+ // Child threads only need to be call into the public interface methods
+ // so we don't bother with initialization
+ return svc->QueryInterface(aIID, aResult);
+ }
+
+ rv = svc->Init();
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
+ }
+
+ // We treat init failure the same as the service being disabled, since this
+ // is all an optimization anyway. No need to freak people out. That's why we
+ // gladly continue on QI'ing here.
+ rv = svc->QueryInterface(aIID, aResult);
+
+ return rv;
+}
+
+// Called from the main thread to initiate predictive actions
+NS_IMETHODIMP
+Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorPredictReason reason, nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Predict"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ ipc::OptionalURIParams serTargetURI, serSourceURI;
+ SerializeURI(targetURI, serTargetURI);
+ SerializeURI(sourceURI, serSourceURI);
+
+ IPC::SerializedLoadContext serLoadContext;
+ serLoadContext.Init(loadContext);
+
+ // If two different threads are predicting concurently, this will be
+ // overwritten. Thankfully, we only use this in tests, which will
+ // overwrite mVerifier perhaps multiple times for each individual test;
+ // however, within each test, the multiple predict calls should have the
+ // same verifier.
+ if (verifier) {
+ PREDICTOR_LOG((" was given a verifier"));
+ mChildVerifier = verifier;
+ }
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredPredict(serTargetURI, serSourceURI,
+ reason, serLoadContext, verifier);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (loadContext && loadContext->UsePrivateBrowsing()) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ // Nothing we can do for non-HTTP[S] schemes
+ PREDICTOR_LOG((" got non-http[s] URI"));
+ return NS_OK;
+ }
+
+ // Ensure we've been given the appropriate arguments for the kind of
+ // prediction we're being asked to do
+ nsCOMPtr<nsIURI> uriKey = targetURI;
+ nsCOMPtr<nsIURI> originKey;
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LINK:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" link invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Link hover is a special case where we can predict without hitting the
+ // db, so let's go ahead and fire off that prediction here.
+ PredictForLink(targetURI, sourceURI, verifier);
+ return NS_OK;
+ case nsINetworkPredictor::PREDICT_LOAD:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ if (targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Predictor::Reason argReason;
+ argReason.mPredict = reason;
+
+ // First we open the regular cache entry, to ensure we don't gum up the works
+ // waiting on the less-important predictor-only cache entry
+ RefPtr<Predictor::Action> uriAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_PREDICT, argReason, targetURI,
+ nullptr, verifier, this);
+ nsAutoCString uriKeyStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
+ reason, uriAction.get()));
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
+
+ // Now we do the origin-only (and therefore predictor-only) entry
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsresult rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!originKey) {
+ originKey = targetOrigin;
+ }
+
+ RefPtr<Predictor::Action> originAction =
+ new Predictor::Action(Predictor::Action::IS_ORIGIN,
+ Predictor::Action::DO_PREDICT, argReason,
+ targetOrigin, nullptr, verifier, this);
+ nsAutoCString originKeyStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p", originKeyStr.get(),
+ reason, originAction.get()));
+ openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(originKey,
+ NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
+ openFlags, originAction);
+
+ PREDICTOR_LOG((" predict returning"));
+ return NS_OK;
+}
+
+bool
+Predictor::PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsINetworkPredictorVerifier *verifier,
+ uint8_t stackCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictInternal"));
+ bool rv = false;
+
+ if (reason == nsINetworkPredictor::PREDICT_LOAD) {
+ MaybeLearnForStartup(targetURI, fullUri);
+ }
+
+ if (isNew) {
+ // nothing else we can do here
+ PREDICTOR_LOG((" new entry"));
+ return rv;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LOAD:
+ rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ rv = PredictForStartup(entry, fullUri, verifier);
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ MOZ_ASSERT(false, "Got unexpected value for prediction reason");
+ }
+
+ return rv;
+}
+
+void
+Predictor::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForLink"));
+ if (!mSpeculativeService) {
+ PREDICTOR_LOG((" missing speculative service"));
+ return;
+ }
+
+ if (!mEnableHoverOnSSL) {
+ bool isSSL = false;
+ sourceURI->SchemeIs("https", &isSSL);
+ if (isSSL) {
+ // We don't want to predict from an HTTPS page, to avoid info leakage
+ PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
+ return;
+ }
+ }
+
+ mSpeculativeService->SpeculativeConnect2(targetURI, nullptr, nullptr);
+ if (verifier) {
+ PREDICTOR_LOG((" sending verification"));
+ verifier->OnPredictPreconnect(targetURI);
+ }
+}
+
+// This is the driver for prediction based on a new pageload.
+static const uint8_t MAX_PAGELOAD_DEPTH = 10;
+bool
+Predictor::PredictForPageload(nsICacheEntry *entry, nsIURI *targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
+ if (stackCount > MAX_PAGELOAD_DEPTH) {
+ PREDICTOR_LOG((" exceeded recursion depth!"));
+ return false;
+ }
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
+ PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
+
+ int32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> redirectURI;
+ if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
+ getter_AddRefs(redirectURI))) {
+ mPreconnects.AppendElement(redirectURI);
+ Predictor::Reason reason;
+ reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
+ RefPtr<Predictor::Action> redirectAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_PREDICT, reason, redirectURI,
+ nullptr, verifier, this, stackCount + 1);
+ nsAutoCString redirectUriString;
+ redirectURI->GetAsciiSpec(redirectUriString);
+ PREDICTOR_LOG((" Predict redirect uri=%s action=%p", redirectUriString.get(),
+ redirectAction.get()));
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ mCacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
+ redirectAction);
+ return RunPredictions(nullptr, verifier);
+ }
+
+ CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, fullUri);
+
+ return RunPredictions(targetURI, verifier);
+}
+
+// This is the driver for predicting at browser startup time based on pages that
+// have previously been loaded close to startup.
+bool
+Predictor::PredictForStartup(nsICacheEntry *entry, bool fullUri,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForStartup"));
+ int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
+ CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
+ globalDegradation, fullUri);
+ return RunPredictions(nullptr, verifier);
+}
+
+// This calculates how much to degrade our confidence in our data based on
+// the last time this top-level resource was loaded. This "global degradation"
+// applies to *all* subresources we have associated with the top-level
+// resource. This will be in addition to any reduction in confidence we have
+// associated with a particular subresource.
+int32_t
+Predictor::CalculateGlobalDegradation(uint32_t lastLoad)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int32_t globalDegradation;
+ uint32_t delta = NOW_IN_SECONDS() - lastLoad;
+ if (delta < ONE_DAY) {
+ globalDegradation = mPageDegradationDay;
+ } else if (delta < ONE_WEEK) {
+ globalDegradation = mPageDegradationWeek;
+ } else if (delta < ONE_MONTH) {
+ globalDegradation = mPageDegradationMonth;
+ } else if (delta < ONE_YEAR) {
+ globalDegradation = mPageDegradationYear;
+ } else {
+ globalDegradation = mPageDegradationMax;
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
+ globalDegradation);
+ return globalDegradation;
+}
+
+// This calculates our overall confidence that a particular subresource will be
+// loaded as part of a top-level load.
+// @param hitCount - the number of times we have loaded this subresource as part
+// of this top-level load
+// @param hitsPossible - the number of times we have performed this top-level
+// load
+// @param lastHit - the timestamp of the last time we loaded this subresource as
+// part of this top-level load
+// @param lastPossible - the timestamp of the last time we performed this
+// top-level load
+// @param globalDegradation - the degradation for this top-level load as
+// determined by CalculateGlobalDegradation
+int32_t
+Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED> predictionsCalculated;
+ ++predictionsCalculated;
+
+ if (!hitsPossible) {
+ return 0;
+ }
+
+ int32_t baseConfidence = (hitCount * 100) / hitsPossible;
+ int32_t maxConfidence = 100;
+ int32_t confidenceDegradation = 0;
+
+ if (lastHit < lastPossible) {
+ // We didn't load this subresource the last time this top-level load was
+ // performed, so let's not bother preconnecting (at the very least).
+ maxConfidence = mPreconnectMinConfidence - 1;
+
+ // Now calculate how much we want to degrade our confidence based on how
+ // long it's been between the last time we did this top-level load and the
+ // last time this top-level load included this subresource.
+ PRTime delta = lastPossible - lastHit;
+ if (delta == 0) {
+ confidenceDegradation = 0;
+ } else if (delta < ONE_DAY) {
+ confidenceDegradation = mSubresourceDegradationDay;
+ } else if (delta < ONE_WEEK) {
+ confidenceDegradation = mSubresourceDegradationWeek;
+ } else if (delta < ONE_MONTH) {
+ confidenceDegradation = mSubresourceDegradationMonth;
+ } else if (delta < ONE_YEAR) {
+ confidenceDegradation = mSubresourceDegradationYear;
+ } else {
+ confidenceDegradation = mSubresourceDegradationMax;
+ maxConfidence = 0;
+ }
+ }
+
+ // Calculate our confidence and clamp it to between 0 and maxConfidence
+ // (<= 100)
+ int32_t confidence = baseConfidence - confidenceDegradation - globalDegradation;
+ confidence = std::max(confidence, 0);
+ confidence = std::min(confidence, maxConfidence);
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
+ confidenceDegradation);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
+ return confidence;
+}
+
+static void
+MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
+ const uint32_t flags, nsCString &newValue)
+{
+ newValue.Truncate();
+ newValue.AppendInt(METADATA_VERSION);
+ newValue.Append(',');
+ newValue.AppendInt(hitCount);
+ newValue.Append(',');
+ newValue.AppendInt(lastHit);
+ newValue.Append(',');
+ newValue.AppendInt(flags);
+}
+
+// On every page load, the rolling window gets shifted by one bit, leaving the
+// lowest bit at 0, to indicate that the subresource in question has not been
+// seen on the most recent page load. If, at some point later during the page load,
+// the subresource is seen again, we will then set the lowest bit to 1. This is
+// how we keep track of how many of the last n pageloads (for n <= 20) a particular
+// subresource has been seen.
+// The rolling window is kept in the upper 20 bits of the flags element of the
+// metadata. This saves 12 bits for regular old flags.
+void
+Predictor::UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags,
+ const char *key, const uint32_t hitCount,
+ const uint32_t lastHit)
+{
+ // Extract just the rolling load count from the flags, shift it to clear the
+ // lowest bit, and put the new value with the existing flags.
+ uint32_t rollingLoadCount = flags & ~kFlagsMask;
+ rollingLoadCount <<= 1;
+ uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
+
+ // Finally, update the metadata on the cache entry.
+ nsAutoCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+}
+
+void
+Predictor::SanitizePrefs()
+{
+ if (mPrefetchRollingLoadCount < 0) {
+ mPrefetchRollingLoadCount = 0;
+ } else if (mPrefetchRollingLoadCount > kMaxPrefetchRollingLoadCount) {
+ mPrefetchRollingLoadCount = kMaxPrefetchRollingLoadCount;
+ }
+}
+
+void
+Predictor::CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ SanitizePrefs();
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
+ keysToOperateOn.SwapElements(mKeysToOperateOn);
+ valuesToOperateOn.SwapElements(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char *key = keysToOperateOn[i].BeginReading();
+ const char *value = valuesToOperateOn[i].BeginReading();
+
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, getter_AddRefs(uri), hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+
+ int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
+ lastLoad, globalDegradation);
+ if (fullUri) {
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, value, confidence));
+ if (!fullUri) {
+ // Not full URI - don't prefetch! No sense in it!
+ PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
+ flags &= ~FLAG_PREFETCHABLE;
+ } else if (!referrer) {
+ // No referrer means we can't prefetch, so pretend it's non-cacheable,
+ // no matter what.
+ PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
+ flags &= ~FLAG_PREFETCHABLE;
+ } else {
+ uint32_t expectedRollingLoadCount = (1 << mPrefetchRollingLoadCount) - 1;
+ expectedRollingLoadCount <<= kRollingLoadOffset;
+ if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
+ PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ }
+
+ PREDICTOR_LOG((" setting up prediction"));
+ SetupPrediction(confidence, flags, uri);
+ }
+}
+
+// (Maybe) adds a predictive action to the prediction runner, based on our
+// calculated confidence for the subresource in question.
+void
+Predictor::SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString uriStr;
+ uri->GetAsciiSpec(uriStr);
+ PREDICTOR_LOG(("SetupPrediction mEnablePrefetch=%d mPrefetchMinConfidence=%d "
+ "mPreconnectMinConfidence=%d mPreresolveMinConfidence=%d "
+ "flags=%d confidence=%d uri=%s", mEnablePrefetch,
+ mPrefetchMinConfidence, mPreconnectMinConfidence,
+ mPreresolveMinConfidence, flags, confidence, uriStr.get()));
+ if (mEnablePrefetch && (flags & FLAG_PREFETCHABLE) &&
+ (mPrefetchRollingLoadCount || (confidence >= mPrefetchMinConfidence))) {
+ mPrefetches.AppendElement(uri);
+ } else if (confidence >= mPreconnectMinConfidence) {
+ mPreconnects.AppendElement(uri);
+ } else if (confidence >= mPreresolveMinConfidence) {
+ mPreresolves.AppendElement(uri);
+ }
+}
+
+nsresult
+Predictor::Prefetch(nsIURI *uri, nsIURI *referrer,
+ nsINetworkPredictorVerifier *verifier)
+{
+ nsAutoCString strUri, strReferrer;
+ uri->GetAsciiSpec(strUri);
+ referrer->GetAsciiSpec(strReferrer);
+ PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
+ strUri.get(), strReferrer.get(), verifier));
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, /* aLoadGroup */
+ nullptr, /* aCallbacks */
+ nsIRequest::LOAD_BACKGROUND);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" NS_NewChannel failed rv=0x%X", rv));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ httpChannel->SetReferrer(referrer);
+ // XXX - set a header here to indicate this is a prefetch?
+
+ nsCOMPtr<nsIStreamListener> listener = new PrefetchListener(verifier, uri,
+ this);
+ PREDICTOR_LOG((" calling AsyncOpen2 listener=%p channel=%p", listener.get(),
+ channel.get()));
+ rv = channel->AsyncOpen2(listener);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" AsyncOpen2 failed rv=0x%X", rv));
+ }
+
+ return rv;
+}
+
+// Runs predictions that have been set up.
+bool
+Predictor::RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
+
+ PREDICTOR_LOG(("Predictor::RunPredictions"));
+
+ bool predicted = false;
+ uint32_t len, i;
+
+ nsTArray<nsCOMPtr<nsIURI>> prefetches, preconnects, preresolves;
+ prefetches.SwapElements(mPrefetches);
+ preconnects.SwapElements(mPreconnects);
+ preresolves.SwapElements(mPreresolves);
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS> totalPredictions;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS> totalPreconnects;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES> totalPreresolves;
+
+ len = prefetches.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing prefetch"));
+ nsCOMPtr<nsIURI> uri = prefetches[i];
+ if (NS_SUCCEEDED(Prefetch(uri, referrer, verifier))) {
+ ++totalPredictions;
+ ++totalPrefetches;
+ predicted = true;
+ }
+ }
+
+ len = preconnects.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing preconnect"));
+ nsCOMPtr<nsIURI> uri = preconnects[i];
+ ++totalPredictions;
+ ++totalPreconnects;
+ mSpeculativeService->SpeculativeConnect2(uri, nullptr, this);
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preconnect verification"));
+ verifier->OnPredictPreconnect(uri);
+ }
+ }
+
+ len = preresolves.Length();
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ for (i = 0; i < len; ++i) {
+ nsCOMPtr<nsIURI> uri = preresolves[i];
+ ++totalPredictions;
+ ++totalPreresolves;
+ nsAutoCString hostname;
+ uri->GetAsciiHost(hostname);
+ PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
+ nsCOMPtr<nsICancelable> tmpCancelable;
+ mDnsService->AsyncResolve(hostname,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ mDNSListener, nullptr,
+ getter_AddRefs(tmpCancelable));
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preresolve verification"));
+ verifier->OnPredictDNS(uri);
+ }
+ }
+
+ return predicted;
+}
+
+// Find out if a top-level page is likely to redirect.
+bool
+Predictor::WouldRedirect(nsICacheEntry *entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI **redirectURI)
+{
+ // TODO - not doing redirects for first go around
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return false;
+}
+
+// Called from the main thread to update the database
+NS_IMETHODIMP
+Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Learn"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ ipc::URIParams serTargetURI;
+ SerializeURI(targetURI, serTargetURI);
+
+ ipc::OptionalURIParams serSourceURI;
+ SerializeURI(sourceURI, serSourceURI);
+
+ IPC::SerializedLoadContext serLoadContext;
+ serLoadContext.Init(loadContext);
+
+ PREDICTOR_LOG((" forwarding to parent"));
+ gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, reason,
+ serLoadContext);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (loadContext && loadContext->UsePrivateBrowsing()) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ PREDICTOR_LOG((" got non-HTTP[S] URI"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsCOMPtr<nsIURI> sourceOrigin;
+ nsCOMPtr<nsIURI> uriKey;
+ nsCOMPtr<nsIURI> originKey;
+ nsresult rv;
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load toplevel invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = targetURI;
+ originKey = targetOrigin;
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" redirect/subresource invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = sourceURI;
+ originKey = sourceOrigin;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
+ ++learnAttempts;
+
+ Predictor::Reason argReason;
+ argReason.mLearn = reason;
+
+ // We always open the full uri (general cache) entry first, so we don't gum up
+ // the works waiting on predictor-only entries to open
+ RefPtr<Predictor::Action> uriAction =
+ new Predictor::Action(Predictor::Action::IS_FULL_URI,
+ Predictor::Action::DO_LEARN, argReason, targetURI,
+ sourceURI, nullptr, this);
+ nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ targetURI->GetAsciiSpec(targetUriStr);
+ if (sourceURI) {
+ sourceURI->GetAsciiSpec(sourceUriStr);
+ }
+ PREDICTOR_LOG((" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
+ "action=%p", uriKeyStr.get(), targetUriStr.get(),
+ sourceUriStr.get(), reason, uriAction.get()));
+ // For learning full URI things, we *always* open readonly and secretly, as we
+ // rely on actual pageloads to update the entry's metadata for us.
+ uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // Learning for toplevel we want to open the full uri entry priority, since
+ // it's likely this entry will be used soon anyway, and we want this to be
+ // opened ASAP.
+ uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+ mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags,
+ uriAction);
+
+ // Now we open the origin-only (and therefore predictor-only) entry
+ RefPtr<Predictor::Action> originAction =
+ new Predictor::Action(Predictor::Action::IS_ORIGIN,
+ Predictor::Action::DO_LEARN, argReason, targetOrigin,
+ sourceOrigin, nullptr, this);
+ nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ targetOrigin->GetAsciiSpec(targetOriginStr);
+ if (sourceOrigin) {
+ sourceOrigin->GetAsciiSpec(sourceOriginStr);
+ }
+ PREDICTOR_LOG((" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
+ "action=%p", originKeyStr.get(), targetOriginStr.get(),
+ sourceOriginStr.get(), reason, originAction.get()));
+ uint32_t originOpenFlags;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // This is the only case when we want to update the 'last used' metadata on
+ // the cache entry we're getting. This only applies to predictor-specific
+ // entries.
+ originOpenFlags = nsICacheStorage::OPEN_NORMALLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ } else {
+ originOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ }
+ mCacheDiskStorage->AsyncOpenURI(originKey,
+ NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
+ originOpenFlags, originAction);
+
+ PREDICTOR_LOG(("Predictor::Learn returning"));
+ return NS_OK;
+}
+
+void
+Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsIURI *sourceURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnInternal"));
+
+ nsCString junk;
+ if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
+ NS_FAILED(entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
+ // This is an origin-only entry that we haven't seen before. Let's mark it
+ // as seen.
+ PREDICTOR_LOG((" marking new origin entry as seen"));
+ nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to mark origin entry seen"));
+ return;
+ }
+
+ // Need to ensure someone else can get to the entry if necessary
+ entry->MetaDataReady();
+ return;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ // This case only exists to be used during tests - code outside the
+ // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
+ // The predictor xpcshell test needs this branch, however, because we
+ // have no real page loads in xpcshell, and this is how we fake it up
+ // so that all the work that normally happens behind the scenes in a
+ // page load can be done for testing purposes.
+ if (fullUri && mDoingTests) {
+ PREDICTOR_LOG((" WARNING - updating rolling load count. "
+ "If you see this outside tests, you did it wrong"));
+ SanitizePrefs();
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
+ keysToOperateOn.SwapElements(mKeysToOperateOn);
+ valuesToOperateOn.SwapElements(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char *key = keysToOperateOn[i].BeginReading();
+ const char *value = valuesToOperateOn[i].BeginReading();
+
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(nullptr, value, nullptr, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ } else {
+ PREDICTOR_LOG((" nothing to do for toplevel"));
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ if (fullUri) {
+ LearnForRedirect(entry, targetURI);
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ LearnForSubresource(entry, targetURI);
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ LearnForStartup(entry, targetURI);
+ break;
+ default:
+ PREDICTOR_LOG((" unexpected reason value"));
+ MOZ_ASSERT(false, "Got unexpected value for learn reason!");
+ }
+}
+
+NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
+
+NS_IMETHODIMP
+Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(key)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ uint32_t hitCount, lastHit, flags;
+ bool ok = mPredictor->ParseMetaDataEntry(nullptr, value, nullptr,
+ hitCount, lastHit, flags);
+
+ if (!ok) {
+ // Couldn't parse this one, just get rid of it
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ nsCString uri(key + (sizeof(META_DATA_PREFIX) - 1));
+ uint32_t uriLength = uri.Length();
+ if (uriLength > mPredictor->mMaxURILength) {
+ // Default to getting rid of URIs that are too long and were put in before
+ // we had our limit on URI length, in order to free up some space.
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
+ mLRUKeyToDelete = key;
+ mLRUStamp = lastHit;
+ }
+
+ return NS_OK;
+}
+
+void
+Predictor::SpaceCleaner::Finalize(nsICacheEntry *entry)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLRUKeyToDelete) {
+ entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
+ }
+
+ for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
+ entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
+ }
+}
+
+// Called when a subresource has been hit from a top-level load. Uses the two
+// helper functions above to update the database appropriately.
+void
+Predictor::LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnForSubresource"));
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ RETURN_IF_FAILED(rv);
+
+ int32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ RETURN_IF_FAILED(rv);
+
+ nsCString key;
+ key.AssignLiteral(META_DATA_PREFIX);
+ nsCString uri;
+ targetURI->GetAsciiSpec(uri);
+ key.Append(uri);
+ if (uri.Length() > mMaxURILength) {
+ // We do this to conserve space/prevent OOMs
+ PREDICTOR_LOG((" uri too long!"));
+ entry->SetMetaDataElement(key.BeginReading(), nullptr);
+ return;
+ }
+
+ nsCString value;
+ rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
+
+ uint32_t hitCount, lastHit, flags;
+ bool isNewResource = (NS_FAILED(rv) ||
+ !ParseMetaDataEntry(nullptr, value.BeginReading(),
+ nullptr, hitCount, lastHit, flags));
+
+ int32_t resourceCount = 0;
+ if (isNewResource) {
+ // This is a new addition
+ PREDICTOR_LOG((" new resource"));
+ nsCString s;
+ rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
+ if (NS_SUCCEEDED(rv)) {
+ resourceCount = atoi(s.BeginReading());
+ }
+ if (resourceCount >= mMaxResourcesPerEntry) {
+ RefPtr<Predictor::SpaceCleaner> cleaner =
+ new Predictor::SpaceCleaner(this);
+ entry->VisitMetaData(cleaner);
+ cleaner->Finalize(entry);
+ } else {
+ ++resourceCount;
+ }
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to update resource count"));
+ return;
+ }
+ hitCount = 1;
+ flags = 0;
+ } else {
+ PREDICTOR_LOG((" existing resource"));
+ hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
+ }
+
+ // Update the rolling load count to mark this sub-resource as seen on the
+ // most-recent pageload so it can be eligible for prefetch (assuming all
+ // the other stars align).
+ flags |= (1 << kRollingLoadOffset);
+
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
+ rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
+ PREDICTOR_LOG((" SetMetaDataElement -> 0x%08X", rv));
+ if (NS_FAILED(rv) && isNewResource) {
+ // Roll back the increment to the resource count we made above.
+ PREDICTOR_LOG((" rolling back resource count update"));
+ --resourceCount;
+ if (resourceCount == 0) {
+ entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
+ } else {
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ }
+ }
+}
+
+// This is called when a top-level loaded ended up redirecting to a different
+// URI so we can keep track of that fact.
+void
+Predictor::LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing redirects for first go around
+ PREDICTOR_LOG(("Predictor::LearnForRedirect"));
+}
+
+// This will add a page to our list of startup pages if it's being loaded
+// before our startup window has expired.
+void
+Predictor::MaybeLearnForStartup(nsIURI *uri, bool fullUri)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing startup for first go around
+ PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
+}
+
+// Add information about a top-level load to our list of startup pages
+void
+Predictor::LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // These actually do the same set of work, just on different entries, so we
+ // can pass through to get the real work done here
+ PREDICTOR_LOG(("Predictor::LearnForStartup"));
+ LearnForSubresource(entry, targetURI);
+}
+
+bool
+Predictor::ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri,
+ uint32_t &hitCount, uint32_t &lastHit,
+ uint32_t &flags)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::ParseMetaDataEntry key=%s value=%s",
+ key ? key : "", value));
+
+ const char *comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find first comma"));
+ return false;
+ }
+
+ uint32_t version = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" version -> %u", version));
+
+ if (version != METADATA_VERSION) {
+ PREDICTOR_LOG((" metadata version mismatch %u != %u", version,
+ METADATA_VERSION));
+ return false;
+ }
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find second comma"));
+ return false;
+ }
+
+ hitCount = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" hitCount -> %u", hitCount));
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find third comma"));
+ return false;
+ }
+
+ lastHit = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" lastHit -> %u", lastHit));
+
+ value = comma + 1;
+ flags = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" flags -> %u", flags));
+
+ if (key) {
+ const char *uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
+ nsresult rv = NS_NewURI(uri, uriStart, nullptr, mIOService);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" NS_NewURI returned 0x%X", rv));
+ return false;
+ }
+ PREDICTOR_LOG((" uri -> %s", uriStart));
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+Predictor::Reset()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Reset"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredReset();
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
+ PREDICTOR_LOG((" created a resetter"));
+ mCacheDiskStorage->AsyncVisitStorage(reset, true);
+ PREDICTOR_LOG((" Cache async launched, returning now"));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor::Resetter,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor,
+ nsICacheStorageVisitor);
+
+Predictor::Resetter::Resetter(Predictor *predictor)
+ :mEntriesToVisit(0)
+ ,mPredictor(predictor)
+{ }
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(result)) {
+ // This can happen when we've tried to open an entry that doesn't exist for
+ // some non-reset operation, and then get reset shortly thereafter (as
+ // happens in some of our tests).
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+ return NS_OK;
+ }
+
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToDelete;
+ keysToDelete.SwapElements(mKeysToDelete);
+
+ for (size_t i = 0; i < keysToDelete.Length(); ++i) {
+ const char *key = keysToDelete[i].BeginReading();
+ entry->SetMetaDataElement(key, nullptr);
+ }
+
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnMetaDataElement(const char *asciiKey,
+ const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StringBeginsWith(nsDependentCString(asciiKey),
+ NS_LITERAL_CSTRING(META_DATA_PREFIX))) {
+ // Not a metadata entry we care about, carry on
+ return NS_OK;
+ }
+
+ nsCString key;
+ key.AssignASCII(asciiKey);
+ mKeysToDelete.AppendElement(key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, uint64_t consumption,
+ uint64_t capacity, nsIFile *diskDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryInfo(nsIURI *uri, const nsACString &idEnhance,
+ int64_t dataSize, int32_t fetchCount,
+ uint32_t lastModifiedTime, uint32_t expirationTime,
+ bool aPinned)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The predictor will only ever touch entries with no idEnhance ("") or an
+ // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
+ // don't match that to avoid doing extra work.
+ if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
+ // This is an entry we own, so we can just doom it entirely
+ mPredictor->mCacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
+ } else if (idEnhance.IsEmpty()) {
+ // This is an entry we don't own, so we have to be a little more careful and
+ // just get rid of our own metadata entries. Append it to an array of things
+ // to operate on and then do the operations later so we don't end up calling
+ // Complete() multiple times/too soon.
+ ++mEntriesToVisit;
+ mURIsToVisit.AppendElement(uri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryVisitCompleted()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsTArray<nsCOMPtr<nsIURI>> urisToVisit;
+ urisToVisit.SwapElements(mURIsToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+
+ uint32_t entriesToVisit = urisToVisit.Length();
+ for (uint32_t i = 0; i < entriesToVisit; ++i) {
+ nsCString u;
+ urisToVisit[i]->GetAsciiSpec(u);
+ mPredictor->mCacheDiskStorage->AsyncOpenURI(
+ urisToVisit[i], EmptyCString(),
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ }
+
+ return NS_OK;
+}
+
+void
+Predictor::Resetter::Complete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
+ return;
+ }
+
+ obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
+}
+
+// Helper functions to make using the predictor easier from native code
+
+static nsresult
+EnsureGlobalPredictor(nsINetworkPredictor **aPredictor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ predictor.forget(aPredictor);
+ return NS_OK;
+}
+
+nsresult
+PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorPredictReason reason, nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Predict(targetURI, sourceURI, reason,
+ loadContext, verifier);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ loadContext = do_GetInterface(callbacks);
+ }
+ }
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsIDocument *document)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+
+ if (document) {
+ loadContext = document->GetLoadContext();
+ }
+
+ return predictor->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
+ nsILoadContext *loadContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> sourceURI;
+ nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameUri;
+ rv = targetURI->Equals(sourceURI, &sameUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (sameUri) {
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->Learn(targetURI, sourceURI,
+ nsINetworkPredictor::LEARN_LOAD_REDIRECT,
+ loadContext);
+}
+
+// nsINetworkPredictorVerifier
+
+/**
+ * Call through to the child's verifier (only during tests)
+ */
+NS_IMETHODIMP
+Predictor::OnPredictPrefetch(nsIURI *aURI, uint32_t httpStatus)
+{
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPrefetch(serURI, httpStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictPreconnect(nsIURI *aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPreconnect(aURI);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPreconnect(serURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictDNS(nsIURI *aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child process
+ // will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictDNS(aURI);
+ }
+ return NS_OK;
+ }
+
+ ipc::URIParams serURI;
+ SerializeURI(aURI, serURI);
+
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictDNS(serURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Predictor::PrefetchListener
+// nsISupports
+NS_IMPL_ISUPPORTS(Predictor::PrefetchListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+// nsIRequestObserver
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ mStartTime = TimeStamp::Now();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%X", this, aStatusCode));
+ NS_ENSURE_ARG(aRequest);
+ if (NS_FAILED(aStatusCode)) {
+ return aStatusCode;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, mStartTime);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
+ if (!cachingChannel) {
+ PREDICTOR_LOG((" Could not get caching channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = cachingChannel->ForceCacheEntryValidFor(mPredictor->mPrefetchForceValidFor);
+ PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%X",
+ mPredictor->mPrefetchForceValidFor, rv));
+ } else {
+ rv = cachingChannel->ForceCacheEntryValidFor(0);
+ PREDICTOR_LOG((" removing any forced validity rv=%X", rv));
+ }
+
+ nsAutoCString reqName;
+ rv = aRequest->GetName(reqName);
+ if (NS_FAILED(rv)) {
+ reqName.AssignLiteral("<unknown>");
+ }
+
+ PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
+
+ if (mVerifier) {
+ mVerifier->OnPredictPrefetch(mURI, httpStatus);
+ }
+
+ return rv;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ const uint32_t aCount)
+{
+ uint32_t result;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+// Miscellaneous Predictor
+
+void
+Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead &requestHead,
+ nsHttpResponseHead *responseHead,
+ nsILoadContextInfo *lci)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (lci && lci->IsPrivate()) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
+ return;
+ }
+
+ if (!sourceURI || !targetURI) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability missing source or target uri"));
+ return;
+ }
+
+ if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
+ return;
+ }
+
+ RefPtr<Predictor> self = sSelf;
+ if (self) {
+ nsAutoCString method;
+ requestHead.Method(method);
+ self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus,
+ method);
+ }
+}
+
+void
+Predictor::UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ const nsCString &method)
+{
+ PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return;
+ }
+
+ if (!mEnabled) {
+ PREDICTOR_LOG((" not enabled"));
+ return;
+ }
+
+ if (!mEnablePrefetch) {
+ PREDICTOR_LOG((" prefetch not enabled"));
+ return;
+ }
+
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ RefPtr<Predictor::CacheabilityAction> action =
+ new Predictor::CacheabilityAction(targetURI, httpStatus, method, this);
+ nsAutoCString uri;
+ targetURI->GetAsciiSpec(uri);
+ PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
+ mCacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action);
+}
+
+NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction,
+ nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor);
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry *entry,
+ nsIApplicationCache *appCache,
+ uint32_t *result)
+{
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry *entry,
+ bool isNew,
+ nsIApplicationCache *appCache,
+ nsresult result)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // This is being opened read-only, so isNew should always be false
+ MOZ_ASSERT(!isNew);
+
+ PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
+ if (NS_FAILED(result)) {
+ // Nothing to do
+ PREDICTOR_LOG((" nothing to do result=%X isNew=%d", result, isNew));
+ return NS_OK;
+ }
+
+ nsresult rv = entry->VisitMetaData(this);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" VisitMetaData returned %x", rv));
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> keysToCheck, valuesToCheck;
+ keysToCheck.SwapElements(mKeysToCheck);
+ valuesToCheck.SwapElements(mValuesToCheck);
+
+ MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
+ for (size_t i = 0; i < keysToCheck.Length(); ++i) {
+ const char *key = keysToCheck[i].BeginReading();
+ const char *value = valuesToCheck[i].BeginReading();
+ nsCOMPtr<nsIURI> uri;
+ uint32_t hitCount, lastHit, flags;
+
+ if (!mPredictor->ParseMetaDataEntry(key, value, getter_AddRefs(uri),
+ hitCount, lastHit, flags)) {
+ PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
+ continue;
+ }
+
+ bool eq = false;
+ if (NS_SUCCEEDED(uri->Equals(mTargetURI, &eq)) && eq) {
+ if (mHttpStatus == 200 && mMethod.EqualsLiteral("GET")) {
+ PREDICTOR_LOG((" marking %s cacheable", key));
+ flags |= FLAG_PREFETCHABLE;
+ } else {
+ PREDICTOR_LOG((" marking %s uncacheable", key));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, flags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnMetaDataElement(const char *asciiKey,
+ const char *asciiValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToCheck.AppendElement(key);
+ mValuesToCheck.AppendElement(value);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h
new file mode 100644
index 000000000..69c597598
--- /dev/null
+++ b/netwerk/base/Predictor.h
@@ -0,0 +1,480 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Predictor_h
+#define mozilla_net_Predictor_h
+
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsIDNSListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIStreamListener.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICacheStorage;
+class nsIDNSService;
+class nsIIOService;
+class nsILoadContextInfo;
+class nsITimer;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+class Predictor : public nsINetworkPredictor
+ , public nsIObserver
+ , public nsISpeculativeConnectionOverrider
+ , public nsIInterfaceRequestor
+ , public nsICacheEntryMetaDataVisitor
+ , public nsINetworkPredictorVerifier
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKPREDICTOR
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSINETWORKPREDICTORVERIFIER
+
+ Predictor();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ static void UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead &requestHead,
+ nsHttpResponseHead *reqponseHead,
+ nsILoadContextInfo *lci);
+
+private:
+ virtual ~Predictor();
+
+ // Stores callbacks for a child process predictor (for test purposes)
+ nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier;
+
+ union Reason {
+ PredictorLearnReason mLearn;
+ PredictorPredictReason mPredict;
+ };
+
+ class DNSListener : public nsIDNSListener
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSListener()
+ { }
+
+ private:
+ virtual ~DNSListener()
+ { }
+ };
+
+ class Action : public nsICacheEntryOpenCallback
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ Action(bool fullUri, bool predict, Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier, Predictor *predictor);
+ Action(bool fullUri, bool predict, Reason reason,
+ nsIURI *targetURI, nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier, Predictor *predictor,
+ uint8_t stackCount);
+
+ static const bool IS_FULL_URI = true;
+ static const bool IS_ORIGIN = false;
+
+ static const bool DO_PREDICT = true;
+ static const bool DO_LEARN = false;
+
+ private:
+ virtual ~Action();
+
+ bool mFullUri : 1;
+ bool mPredict : 1;
+ union {
+ PredictorPredictReason mPredictReason;
+ PredictorLearnReason mLearnReason;
+ };
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ TimeStamp mStartTime;
+ uint8_t mStackCount;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class CacheabilityAction : public nsICacheEntryOpenCallback
+ , public nsICacheEntryMetaDataVisitor
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ CacheabilityAction(nsIURI *targetURI, uint32_t httpStatus,
+ const nsCString &method, Predictor *predictor)
+ :mTargetURI(targetURI)
+ ,mHttpStatus(httpStatus)
+ ,mMethod(method)
+ ,mPredictor(predictor)
+ { }
+
+ private:
+ virtual ~CacheabilityAction() { }
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ uint32_t mHttpStatus;
+ nsCString mMethod;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCString> mKeysToCheck;
+ nsTArray<nsCString> mValuesToCheck;
+ };
+
+ class Resetter : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsICacheStorageVisitor
+ {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSICACHESTORAGEVISITOR
+
+ explicit Resetter(Predictor *predictor);
+
+ private:
+ virtual ~Resetter() { }
+
+ void Complete();
+
+ uint32_t mEntriesToVisit;
+ nsTArray<nsCString> mKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit;
+ };
+
+ class SpaceCleaner : public nsICacheEntryMetaDataVisitor
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ explicit SpaceCleaner(Predictor *predictor)
+ :mLRUStamp(0)
+ ,mLRUKeyToDelete(nullptr)
+ ,mPredictor(predictor)
+ { }
+
+ void Finalize(nsICacheEntry *entry);
+
+ private:
+ virtual ~SpaceCleaner() { }
+ uint32_t mLRUStamp;
+ const char *mLRUKeyToDelete;
+ nsTArray<nsCString> mLongKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class PrefetchListener : public nsIStreamListener
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ PrefetchListener(nsINetworkPredictorVerifier *verifier, nsIURI *uri,
+ Predictor *predictor)
+ :mVerifier(verifier)
+ ,mURI(uri)
+ ,mPredictor(predictor)
+ { }
+
+ private:
+ virtual ~PrefetchListener() { }
+
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<Predictor> mPredictor;
+ TimeStamp mStartTime;
+ };
+
+ // Observer-related stuff
+ nsresult InstallObserver();
+ void RemoveObserver();
+
+ // Service startup utilities
+ void MaybeCleanupOldDBFiles();
+
+ // The guts of prediction
+
+ // This is the top-level driver for doing any prediction that needs
+ // information from the cache. Returns true if any predictions were queued up
+ // * reason - What kind of prediction this is/why this prediction is
+ // happening (pageload, startup)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are predicting based upon - IOW, the URI
+ // that is being loaded or being redirected to
+ // * verifier - used for testing to verify the expected predictions happen
+ // * stackCount - used to ensure we don't recurse too far trying to find the
+ // final redirection in a redirect chain
+ bool PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsINetworkPredictorVerifier *verifier,
+ uint8_t stackCount);
+
+ // Used when predicting because the user's mouse hovered over a link
+ // * targetURI - the URI target of the link
+ // * sourceURI - the URI of the page on which the link appears
+ // * verifier - used for testing to verify the expected predictions happen
+ void PredictForLink(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Used when predicting because a page is being loaded (which may include
+ // being the target of a redirect). All arguments are the same as for
+ // PredictInternal. Returns true if any predictions were queued up.
+ bool PredictForPageload(nsICacheEntry *entry,
+ nsIURI *targetURI,
+ uint8_t stackCount,
+ bool fullUri,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Used when predicting pages that will be used near browser startup. All
+ // arguments are the same as for PredictInternal. Returns true if any
+ // predictions were queued up.
+ bool PredictForStartup(nsICacheEntry *entry,
+ bool fullUri,
+ nsINetworkPredictorVerifier *verifier);
+
+ // Utilities related to prediction
+
+ // Used to update our rolling load count (how many of the last n loads was a
+ // partular resource loaded on?)
+ // * entry - cache entry of page we're loading
+ // * flags - value that contains our rolling count as the top 20 bits (but
+ // we may use fewer than those 20 bits for calculations)
+ // * key - metadata key that we will update on entry
+ // * hitCount - part of the metadata we need to preserve
+ // * lastHit - part of the metadata we need to preserve
+ void UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags,
+ const char *key, const uint32_t hitCount,
+ const uint32_t lastHit);
+
+ // Used to calculate how much to degrade our confidence for all resources
+ // on a particular page, because of how long ago the most recent load of that
+ // page was. Returns a value between 0 (very recent most recent load) and 100
+ // (very distant most recent load)
+ // * lastLoad - time stamp of most recent load of a page
+ int32_t CalculateGlobalDegradation(uint32_t lastLoad);
+
+ // Used to calculate how confident we are that a particular resource will be
+ // used. Returns a value between 0 (no confidence) and 100 (very confident)
+ // * hitCount - number of times this resource has been seen when loading
+ // this page
+ // * hitsPossible - number of times this page has been loaded
+ // * lastHit - timestamp of the last time this resource was seen when
+ // loading this page
+ // * lastPossible - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation);
+
+ // Used to calculate all confidence values for all resources associated with a
+ // page.
+ // * entry - the cache entry with all necessary information about this page
+ // * referrer - the URI that we are loading (may be null)
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * loadCount - number of times this page has been loaded
+ // * gloablDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * fullUri - whether we're predicting for a full URI or origin-only
+ void CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri);
+
+ // Used to prepare any necessary prediction for a resource on a page
+ // * confidence - value calculated by CalculateConfidence for this resource
+ // * flags - the flags taken from the resource
+ // * uri - the URI of the resource
+ void SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri);
+
+ // Used to kick off a prefetch from RunPredictions if necessary
+ // * uri - the URI to prefetch
+ // * referrer - the URI of the referring page
+ // * verifier - used for testing to ensure the expected prefetch happens
+ nsresult Prefetch(nsIURI *uri, nsIURI *referrer, nsINetworkPredictorVerifier *verifier);
+
+ // Used to actually perform any predictions set up via SetupPrediction.
+ // Returns true if any predictions were performed.
+ // * referrer - the URI we are predicting from
+ // * verifier - used for testing to ensure the expected predictions happen
+ bool RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier);
+
+ // Used to guess whether a page will redirect to another page or not. Returns
+ // true if a redirection is likely.
+ // * entry - cache entry with all necessary information about this page
+ // * loadCount - number of times this page has been loaded
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * redirectURI - if this returns true, the URI that is likely to be
+ // redirected to, otherwise null
+ bool WouldRedirect(nsICacheEntry *entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI **redirectURI);
+
+ // The guts of learning information
+
+ // This is the top-level driver for doing any updating of our information in
+ // the cache
+ // * reason - why this learn is happening (pageload, startup, redirect)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are adding to our data - most often a
+ // resource loaded by a page the user navigated to
+ // * sourceURI - the URI that caused targetURI to be loaded, usually the
+ // page the user navigated to
+ void LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
+ bool isNew, bool fullUri, nsIURI *targetURI,
+ nsIURI *sourceURI);
+
+ // Used when learning about a resource loaded by a page
+ // * entry - the cache entry with information that needs updating
+ // * targetURI - the URI of the resource that was loaded by the page
+ void LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used when learning about a redirect from one page to another
+ // * entry - the cache entry of the page that was redirected from
+ // * targetURI - the URI of the redirect target
+ void LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used to learn about pages loaded close to browser startup. This results in
+ // LearnForStartup being called if we are, in fact, near browser startup
+ // * uri - the URI of a page that has been loaded (may not have been near
+ // browser startup)
+ // * fullUri - true if this is a full page uri, false if it's an origin
+ void MaybeLearnForStartup(nsIURI *uri, bool fullUri);
+
+ // Used in conjunction with MaybeLearnForStartup to learn about pages loaded
+ // close to browser startup
+ // * entry - the cache entry that stores the startup page list
+ // * targetURI - the URI of a page that was loaded near browser startup
+ void LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI);
+
+ // Used to parse the data we store in cache metadata
+ // * key - the cache metadata key
+ // * value - the cache metadata value
+ // * uri - (out) the URI this metadata entry was about
+ // * hitCount - (out) the number of times this URI has been seen
+ // * lastHit - (out) timestamp of the last time this URI was seen
+ // * flags - (out) flags for this metadata entry
+ bool ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri,
+ uint32_t &hitCount, uint32_t &lastHit,
+ uint32_t &flags);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ void UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI,
+ uint32_t httpStatus, const nsCString &method);
+
+ // Make sure our prefs are in their expected range of values
+ void SanitizePrefs();
+
+ // Our state
+ bool mInitialized;
+
+ bool mEnabled;
+ bool mEnableHoverOnSSL;
+ bool mEnablePrefetch;
+
+ int32_t mPageDegradationDay;
+ int32_t mPageDegradationWeek;
+ int32_t mPageDegradationMonth;
+ int32_t mPageDegradationYear;
+ int32_t mPageDegradationMax;
+
+ int32_t mSubresourceDegradationDay;
+ int32_t mSubresourceDegradationWeek;
+ int32_t mSubresourceDegradationMonth;
+ int32_t mSubresourceDegradationYear;
+ int32_t mSubresourceDegradationMax;
+
+ int32_t mPrefetchRollingLoadCount;
+ int32_t mPrefetchMinConfidence;
+ int32_t mPreconnectMinConfidence;
+ int32_t mPreresolveMinConfidence;
+ int32_t mRedirectLikelyConfidence;
+
+ int32_t mPrefetchForceValidFor;
+
+ int32_t mMaxResourcesPerEntry;
+
+ bool mCleanedUp;
+ nsCOMPtr<nsITimer> mCleanupTimer;
+
+ nsTArray<nsCString> mKeysToOperateOn;
+ nsTArray<nsCString> mValuesToOperateOn;
+
+ nsCOMPtr<nsICacheStorage> mCacheDiskStorage;
+
+ nsCOMPtr<nsIIOService> mIOService;
+ nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+ nsCOMPtr<nsIURI> mStartupURI;
+ uint32_t mStartupTime;
+ uint32_t mLastStartupTime;
+ int32_t mStartupCount;
+
+ uint32_t mMaxURILength;
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+
+ RefPtr<DNSListener> mDNSListener;
+
+ nsTArray<nsCOMPtr<nsIURI>> mPrefetches;
+ nsTArray<nsCOMPtr<nsIURI>> mPreconnects;
+ nsTArray<nsCOMPtr<nsIURI>> mPreresolves;
+
+ bool mDoingTests;
+
+ static Predictor *sSelf;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Predictor_h
diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h
new file mode 100644
index 000000000..10c664502
--- /dev/null
+++ b/netwerk/base/PrivateBrowsingChannel.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PrivateBrowsingChannel_h__
+#define mozilla_net_PrivateBrowsingChannel_h__
+
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsNetUtil.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+template <class Channel>
+class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel
+{
+public:
+ PrivateBrowsingChannel() :
+ mPrivateBrowsingOverriden(false),
+ mPrivateBrowsing(false)
+ {
+ }
+
+ NS_IMETHOD SetPrivate(bool aPrivate)
+ {
+ // Make sure that we don't have a load context
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+ MOZ_ASSERT(!loadContext);
+ if (loadContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrivateBrowsingOverriden = true;
+ mPrivateBrowsing = aPrivate;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIsChannelPrivate(bool *aResult)
+ {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool *aResult)
+ {
+ NS_ENSURE_ARG_POINTER(aValue);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsingOverriden;
+ if (mPrivateBrowsingOverriden) {
+ *aValue = mPrivateBrowsing;
+ }
+ return NS_OK;
+ }
+
+ // Must be called every time the channel's callbacks or loadGroup is updated
+ void UpdatePrivateBrowsing()
+ {
+ // once marked as private we never go un-private
+ if (mPrivateBrowsing) {
+ return;
+ }
+
+ auto channel = static_cast<Channel*>(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ if (loadContext) {
+ mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ Unused << channel->GetLoadInfo(getter_AddRefs(loadInfo));
+ if (loadInfo) {
+ NeckoOriginAttributes attrs = loadInfo->GetOriginAttributes();
+ mPrivateBrowsing = attrs.mPrivateBrowsingId > 0;
+ }
+ }
+
+ bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const
+ {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aCallbacks) {
+ return true;
+ }
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ if (!loadContext) {
+ return true;
+ }
+ MOZ_ASSERT(!mPrivateBrowsingOverriden);
+ return !mPrivateBrowsingOverriden;
+ }
+
+ bool CanSetLoadGroup(nsILoadGroup* aLoadGroup) const
+ {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aLoadGroup) {
+ return true;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ // From this point on, we just hand off the work to CanSetCallbacks,
+ // because the logic is exactly the same.
+ return CanSetCallbacks(callbacks);
+ }
+
+protected:
+ bool mPrivateBrowsingOverriden;
+ bool mPrivateBrowsing;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
+
diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp
new file mode 100644
index 000000000..4d7a6c1fd
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -0,0 +1,1015 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyAutoConfig.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIURLParser.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "prnetdb.h"
+#include "nsITimer.h"
+#include "mozilla/net/DNS.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+namespace mozilla {
+namespace net {
+
+// These are some global helper symbols the PAC format requires that we provide that
+// are initialized as part of the global javascript context used for PAC evaluations.
+// Additionally dnsResolve(host) and myIpAddress() are supplied in the same context
+// but are implemented as c++ helpers. alert(msg) is similarly defined.
+
+static const char *sPacUtils =
+ "function dnsDomainIs(host, domain) {\n"
+ " return (host.length >= domain.length &&\n"
+ " host.substring(host.length - domain.length) == domain);\n"
+ "}\n"
+ ""
+ "function dnsDomainLevels(host) {\n"
+ " return host.split('.').length - 1;\n"
+ "}\n"
+ ""
+ "function convert_addr(ipchars) {\n"
+ " var bytes = ipchars.split('.');\n"
+ " var result = ((bytes[0] & 0xff) << 24) |\n"
+ " ((bytes[1] & 0xff) << 16) |\n"
+ " ((bytes[2] & 0xff) << 8) |\n"
+ " (bytes[3] & 0xff);\n"
+ " return result;\n"
+ "}\n"
+ ""
+ "function isInNet(ipaddr, pattern, maskstr) {\n"
+ " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n"
+ " if (test == null) {\n"
+ " ipaddr = dnsResolve(ipaddr);\n"
+ " if (ipaddr == null)\n"
+ " return false;\n"
+ " } else if (test[1] > 255 || test[2] > 255 || \n"
+ " test[3] > 255 || test[4] > 255) {\n"
+ " return false; // not an IP address\n"
+ " }\n"
+ " var host = convert_addr(ipaddr);\n"
+ " var pat = convert_addr(pattern);\n"
+ " var mask = convert_addr(maskstr);\n"
+ " return ((host & mask) == (pat & mask));\n"
+ " \n"
+ "}\n"
+ ""
+ "function isPlainHostName(host) {\n"
+ " return (host.search('\\\\.') == -1);\n"
+ "}\n"
+ ""
+ "function isResolvable(host) {\n"
+ " var ip = dnsResolve(host);\n"
+ " return (ip != null);\n"
+ "}\n"
+ ""
+ "function localHostOrDomainIs(host, hostdom) {\n"
+ " return (host == hostdom) ||\n"
+ " (hostdom.lastIndexOf(host + '.', 0) == 0);\n"
+ "}\n"
+ ""
+ "function shExpMatch(url, pattern) {\n"
+ " pattern = pattern.replace(/\\./g, '\\\\.');\n"
+ " pattern = pattern.replace(/\\*/g, '.*');\n"
+ " pattern = pattern.replace(/\\?/g, '.');\n"
+ " var newRe = new RegExp('^'+pattern+'$');\n"
+ " return newRe.test(url);\n"
+ "}\n"
+ ""
+ "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n"
+ "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"
+ ""
+ "function weekdayRange() {\n"
+ " function getDay(weekday) {\n"
+ " if (weekday in wdays) {\n"
+ " return wdays[weekday];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " var wday;\n"
+ " if (argc < 1)\n"
+ " return false;\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " argc--;\n"
+ " wday = date.getUTCDay();\n"
+ " } else {\n"
+ " wday = date.getDay();\n"
+ " }\n"
+ " var wd1 = getDay(arguments[0]);\n"
+ " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n"
+ " return (wd1 == -1 || wd2 == -1) ? false\n"
+ " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n"
+ " : (wd2 >= wday || wday >= wd1);\n"
+ "}\n"
+ ""
+ "function dateRange() {\n"
+ " function getMonth(name) {\n"
+ " if (name in months) {\n"
+ " return months[name];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " var isGMT = (arguments[argc - 1] == 'GMT');\n"
+ "\n"
+ " if (isGMT) {\n"
+ " argc--;\n"
+ " }\n"
+ " // function will work even without explict handling of this case\n"
+ " if (argc == 1) {\n"
+ " var tmp = parseInt(arguments[0]);\n"
+ " if (isNaN(tmp)) {\n"
+ " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n"
+ " getMonth(arguments[0]));\n"
+ " } else if (tmp < 32) {\n"
+ " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n"
+ " } else { \n"
+ " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n"
+ " tmp);\n"
+ " }\n"
+ " }\n"
+ " var year = date.getFullYear();\n"
+ " var date1, date2;\n"
+ " date1 = new Date(year, 0, 1, 0, 0, 0);\n"
+ " date2 = new Date(year, 11, 31, 23, 59, 59);\n"
+ " var adjustMonth = false;\n"
+ " for (var i = 0; i < (argc >> 1); i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date1.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " adjustMonth = (argc <= 2);\n"
+ " date1.setDate(tmp);\n"
+ " } else {\n"
+ " date1.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " for (var i = (argc >> 1); i < argc; i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date2.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " date2.setDate(tmp);\n"
+ " } else {\n"
+ " date2.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " if (adjustMonth) {\n"
+ " date1.setMonth(date.getMonth());\n"
+ " date2.setMonth(date.getMonth());\n"
+ " }\n"
+ " if (isGMT) {\n"
+ " var tmp = date;\n"
+ " tmp.setFullYear(date.getUTCFullYear());\n"
+ " tmp.setMonth(date.getUTCMonth());\n"
+ " tmp.setDate(date.getUTCDate());\n"
+ " tmp.setHours(date.getUTCHours());\n"
+ " tmp.setMinutes(date.getUTCMinutes());\n"
+ " tmp.setSeconds(date.getUTCSeconds());\n"
+ " date = tmp;\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "}\n"
+ ""
+ "function timeRange() {\n"
+ " var argc = arguments.length;\n"
+ " var date = new Date();\n"
+ " var isGMT= false;\n"
+ ""
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " isGMT = true;\n"
+ " argc--;\n"
+ " }\n"
+ "\n"
+ " var hour = isGMT ? date.getUTCHours() : date.getHours();\n"
+ " var date1, date2;\n"
+ " date1 = new Date();\n"
+ " date2 = new Date();\n"
+ "\n"
+ " if (argc == 1) {\n"
+ " return (hour == arguments[0]);\n"
+ " } else if (argc == 2) {\n"
+ " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n"
+ " } else {\n"
+ " switch (argc) {\n"
+ " case 6:\n"
+ " date1.setSeconds(arguments[2]);\n"
+ " date2.setSeconds(arguments[5]);\n"
+ " case 4:\n"
+ " var middle = argc >> 1;\n"
+ " date1.setHours(arguments[0]);\n"
+ " date1.setMinutes(arguments[1]);\n"
+ " date2.setHours(arguments[middle]);\n"
+ " date2.setMinutes(arguments[middle + 1]);\n"
+ " if (middle == 2) {\n"
+ " date2.setSeconds(59);\n"
+ " }\n"
+ " break;\n"
+ " default:\n"
+ " throw 'timeRange: bad number of arguments'\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (isGMT) {\n"
+ " date.setFullYear(date.getUTCFullYear());\n"
+ " date.setMonth(date.getUTCMonth());\n"
+ " date.setDate(date.getUTCDate());\n"
+ " date.setHours(date.getUTCHours());\n"
+ " date.setMinutes(date.getUTCMinutes());\n"
+ " date.setSeconds(date.getUTCSeconds());\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "\n"
+ "}\n"
+ "";
+
+// sRunning is defined for the helper functions only while the
+// Javascript engine is running and the PAC object cannot be deleted
+// or reset.
+static uint32_t sRunningIndex = 0xdeadbeef;
+static ProxyAutoConfig *GetRunning()
+{
+ MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
+ return static_cast<ProxyAutoConfig *>(PR_GetThreadPrivate(sRunningIndex));
+}
+
+static void SetRunning(ProxyAutoConfig *arg)
+{
+ MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
+ PR_SetThreadPrivate(sRunningIndex, arg);
+}
+
+// The PACResolver is used for dnsResolve()
+class PACResolver final : public nsIDNSListener
+ , public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ PACResolver()
+ : mStatus(NS_ERROR_FAILURE)
+ {
+ }
+
+ // nsIDNSListener
+ NS_IMETHOD OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *record,
+ nsresult status) override
+ {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mRequest = nullptr;
+ mStatus = status;
+ mResponse = record;
+ return NS_OK;
+ }
+
+ // nsITimerCallback
+ NS_IMETHOD Notify(nsITimer *timer) override
+ {
+ if (mRequest)
+ mRequest->Cancel(NS_ERROR_NET_TIMEOUT);
+ mTimer = nullptr;
+ return NS_OK;
+ }
+
+ nsresult mStatus;
+ nsCOMPtr<nsICancelable> mRequest;
+ nsCOMPtr<nsIDNSRecord> mResponse;
+ nsCOMPtr<nsITimer> mTimer;
+
+private:
+ ~PACResolver() {}
+};
+NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback)
+
+static
+void PACLogToConsole(nsString &aMessage)
+{
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService)
+ return;
+
+ consoleService->LogStringMessage(aMessage.get());
+}
+
+// Javascript errors and warnings are logged to the main error console
+static void
+PACLogErrorOrWarning(const nsAString& aKind, JSErrorReport* aReport)
+{
+ nsString formattedMessage(NS_LITERAL_STRING("PAC Execution "));
+ formattedMessage += aKind;
+ formattedMessage += NS_LITERAL_STRING(": ");
+ if (aReport->message())
+ formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
+ formattedMessage += NS_LITERAL_STRING(" [");
+ formattedMessage.Append(aReport->linebuf(), aReport->linebufLength());
+ formattedMessage += NS_LITERAL_STRING("]");
+ PACLogToConsole(formattedMessage);
+}
+
+static void
+PACWarningReporter(JSContext* aCx, JSErrorReport* aReport)
+{
+ MOZ_ASSERT(aReport);
+ MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags));
+
+ PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport);
+}
+
+class MOZ_STACK_CLASS AutoPACErrorReporter
+{
+ JSContext* mCx;
+
+public:
+ explicit AutoPACErrorReporter(JSContext* aCx)
+ : mCx(aCx)
+ {}
+ ~AutoPACErrorReporter() {
+ if (!JS_IsExceptionPending(mCx)) {
+ return;
+ }
+ JS::RootedValue exn(mCx);
+ if (!JS_GetPendingException(mCx, &exn)) {
+ return;
+ }
+ JS_ClearPendingException(mCx);
+
+ js::ErrorReport report(mCx);
+ if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) {
+ JS_ClearPendingException(mCx);
+ return;
+ }
+
+ PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report());
+ }
+};
+
+// timeout of 0 means the normal necko timeout strategy, otherwise the dns request
+// will be canceled after aTimeout milliseconds
+static
+bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr,
+ unsigned int aTimeout)
+{
+ if (!GetRunning()) {
+ NS_WARNING("PACResolve without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
+}
+
+ProxyAutoConfig::ProxyAutoConfig()
+ : mJSContext(nullptr)
+ , mJSNeedsSetup(false)
+ , mShutdown(false)
+ , mIncludePath(false)
+{
+ MOZ_COUNT_CTOR(ProxyAutoConfig);
+}
+
+bool
+ProxyAutoConfig::ResolveAddress(const nsCString &aHostName,
+ NetAddr *aNetAddr,
+ unsigned int aTimeout)
+{
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return false;
+
+ RefPtr<PACResolver> helper = new PACResolver();
+
+ if (NS_FAILED(dns->AsyncResolve(aHostName,
+ nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
+ helper,
+ NS_GetCurrentThread(),
+ getter_AddRefs(helper->mRequest))))
+ return false;
+
+ if (aTimeout && helper->mRequest) {
+ if (!mTimer)
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (mTimer) {
+ mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
+ helper->mTimer = mTimer;
+ }
+ }
+
+ // Spin the event loop of the pac thread until lookup is complete.
+ // nsPACman is responsible for keeping a queue and only allowing
+ // one PAC execution at a time even when it is called re-entrantly.
+ while (helper->mRequest)
+ NS_ProcessNextEvent(NS_GetCurrentThread());
+
+ if (NS_FAILED(helper->mStatus) ||
+ NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
+ return false;
+ return true;
+}
+
+static
+bool PACResolveToString(const nsCString &aHostName,
+ nsCString &aDottedDecimal,
+ unsigned int aTimeout)
+{
+ NetAddr netAddr;
+ if (!PACResolve(aHostName, &netAddr, aTimeout))
+ return false;
+
+ char dottedDecimal[128];
+ if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal)))
+ return false;
+
+ aDottedDecimal.Assign(dottedDecimal);
+ return true;
+}
+
+// dnsResolve(host) javascript implementation
+static
+bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "dnsResolve", 1))
+ return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1)
+ return false;
+
+ nsAutoJSString hostName;
+ nsAutoCString dottedDecimal;
+
+ if (!hostName.init(cx, arg1))
+ return false;
+ if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
+ JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ args.rval().setString(dottedDecimalString);
+ }
+ else {
+ args.rval().setNull();
+ }
+
+ return true;
+}
+
+// myIpAddress() javascript implementation
+static
+bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!GetRunning()) {
+ NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->MyIPAddress(args);
+}
+
+// proxyAlert(msg) javascript implementation
+static
+bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "alert", 1))
+ return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1)
+ return false;
+
+ nsAutoJSString message;
+ if (!message.init(cx, arg1))
+ return false;
+
+ nsAutoString alertMessage;
+ alertMessage.SetCapacity(32 + message.Length());
+ alertMessage += NS_LITERAL_STRING("PAC-alert: ");
+ alertMessage += message;
+ PACLogToConsole(alertMessage);
+
+ args.rval().setUndefined(); /* return undefined */
+ return true;
+}
+
+static const JSFunctionSpec PACGlobalFunctions[] = {
+ JS_FS("dnsResolve", PACDnsResolve, 1, 0),
+
+ // a global "var pacUseMultihomedDNS = true;" will change behavior
+ // of myIpAddress to actively use DNS
+ JS_FS("myIpAddress", PACMyIpAddress, 0, 0),
+ JS_FS("alert", PACProxyAlert, 1, 0),
+ JS_FS_END
+};
+
+// JSContextWrapper is a c++ object that manages the context for the JS engine
+// used on the PAC thread. It is initialized and destroyed on the PAC thread.
+class JSContextWrapper
+{
+ public:
+ static JSContextWrapper *Create()
+ {
+ JSContext* cx = JS_NewContext(sContextHeapSize);
+ if (NS_WARN_IF(!cx))
+ return nullptr;
+
+ JSContextWrapper *entry = new JSContextWrapper(cx);
+ if (NS_FAILED(entry->Init())) {
+ delete entry;
+ return nullptr;
+ }
+
+ return entry;
+ }
+
+ JSContext *Context() const
+ {
+ return mContext;
+ }
+
+ JSObject *Global() const
+ {
+ return mGlobal;
+ }
+
+ ~JSContextWrapper()
+ {
+ mGlobal = nullptr;
+
+ MOZ_COUNT_DTOR(JSContextWrapper);
+
+ if (mContext) {
+ JS_DestroyContext(mContext);
+ }
+ }
+
+ void SetOK()
+ {
+ mOK = true;
+ }
+
+ bool IsOK()
+ {
+ return mOK;
+ }
+
+private:
+ static const unsigned sContextHeapSize = 4 << 20; // 4 MB
+
+ JSContext *mContext;
+ JS::PersistentRooted<JSObject*> mGlobal;
+ bool mOK;
+
+ static const JSClass sGlobalClass;
+
+ explicit JSContextWrapper(JSContext* cx)
+ : mContext(cx), mGlobal(cx, nullptr), mOK(false)
+ {
+ MOZ_COUNT_CTOR(JSContextWrapper);
+ }
+
+ nsresult Init()
+ {
+ /*
+ * Not setting this will cause JS_CHECK_RECURSION to report false
+ * positives
+ */
+ JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
+
+ JS::SetWarningReporter(mContext, PACWarningReporter);
+
+ if (!JS::InitSelfHostedCode(mContext)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JSAutoRequest ar(mContext);
+
+ JS::CompartmentOptions options;
+ options.creationOptions().setZone(JS::SystemZone);
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ if (!mGlobal) {
+ JS_ClearPendingException(mContext);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JS::Rooted<JSObject*> global(mContext, mGlobal);
+
+ JSAutoCompartment ac(mContext, global);
+ AutoPACErrorReporter aper(mContext);
+ if (!JS_InitStandardClasses(mContext, global)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS_FireOnNewGlobalObject(mContext, global);
+
+ return NS_OK;
+ }
+};
+
+static const JSClassOps sJSContextWrapperGlobalClassOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+};
+
+const JSClass JSContextWrapper::sGlobalClass = {
+ "PACResolutionThreadGlobal",
+ JSCLASS_GLOBAL_FLAGS,
+ &sJSContextWrapperGlobalClassOps
+};
+
+void
+ProxyAutoConfig::SetThreadLocalIndex(uint32_t index)
+{
+ sRunningIndex = index;
+}
+
+nsresult
+ProxyAutoConfig::Init(const nsCString &aPACURI,
+ const nsCString &aPACScript,
+ bool aIncludePath)
+{
+ mPACURI = aPACURI;
+ mPACScript = sPacUtils;
+ mPACScript.Append(aPACScript);
+ mIncludePath = aIncludePath;
+
+ if (!GetRunning())
+ return SetupJS();
+
+ mJSNeedsSetup = true;
+ return NS_OK;
+}
+
+nsresult
+ProxyAutoConfig::SetupJS()
+{
+ mJSNeedsSetup = false;
+ MOZ_ASSERT(!GetRunning(), "JIT is running");
+
+ delete mJSContext;
+ mJSContext = nullptr;
+
+ if (mPACScript.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ mJSContext = JSContextWrapper::Create();
+ if (!mJSContext)
+ return NS_ERROR_FAILURE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // check if this is a data: uri so that we don't spam the js console with
+ // huge meaningless strings. this is not on the main thread, so it can't
+ // use nsIURI scheme methods
+ bool isDataURI = nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
+
+ SetRunning(this);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(mPACURI.get(), 1);
+ JS::Rooted<JSScript*> script(cx);
+ if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options,
+ &script) ||
+ !JS_ExecuteScript(cx, script))
+ {
+ nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from "));
+ if (isDataURI) {
+ alertMessage += NS_LITERAL_STRING("data: URI");
+ }
+ else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+ SetRunning(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ SetRunning(nullptr);
+
+ mJSContext->SetOK();
+ nsString alertMessage(NS_LITERAL_STRING("PAC file installed from "));
+ if (isDataURI) {
+ alertMessage += NS_LITERAL_STRING("data: URI");
+ }
+ else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+
+ // we don't need these now
+ mPACScript.Truncate();
+ mPACURI.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
+ const nsCString &aTestHost,
+ nsACString &result)
+{
+ if (mJSNeedsSetup)
+ SetupJS();
+
+ if (!mJSContext || !mJSContext->IsOK())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ JSContext *cx = mJSContext->Context();
+ JSAutoRequest ar(cx);
+ JSAutoCompartment ac(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // the sRunning flag keeps a new PAC file from being installed
+ // while the event loop is spinning on a DNS function. Don't early return.
+ SetRunning(this);
+ mRunningHost = aTestHost;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCString clensedURI = aTestURI;
+
+ if (!mIncludePath) {
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID);
+ int32_t pathLen = 0;
+ if (urlParser) {
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ rv = urlParser->ParseURL(aTestURI.get(), aTestURI.Length(),
+ &schemePos, &schemeLen,
+ &authorityPos, &authorityLen,
+ &pathPos, &pathLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (pathLen) {
+ // cut off the path but leave the initial slash
+ pathLen--;
+ }
+ aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
+ }
+ }
+
+ JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get()));
+ JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
+
+ if (uriString && hostString) {
+ JS::AutoValueArray<2> args(cx);
+ args[0].setString(uriString);
+ args[1].setString(hostString);
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
+
+ if (ok && rval.isString()) {
+ nsAutoJSString pacString;
+ if (pacString.init(cx, rval.toString())) {
+ CopyUTF16toUTF8(pacString, result);
+ rv = NS_OK;
+ }
+ }
+ }
+
+ mRunningHost.Truncate();
+ SetRunning(nullptr);
+ return rv;
+}
+
+void
+ProxyAutoConfig::GC()
+{
+ if (!mJSContext || !mJSContext->IsOK())
+ return;
+
+ JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global());
+ JS_MaybeGC(mJSContext->Context());
+}
+
+ProxyAutoConfig::~ProxyAutoConfig()
+{
+ MOZ_COUNT_DTOR(ProxyAutoConfig);
+ NS_ASSERTION(!mJSContext,
+ "~ProxyAutoConfig leaking JS context that "
+ "should have been deleted on pac thread");
+}
+
+void
+ProxyAutoConfig::Shutdown()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown");
+
+ if (GetRunning() || mShutdown)
+ return;
+
+ mShutdown = true;
+ delete mJSContext;
+ mJSContext = nullptr;
+}
+
+bool
+ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress)
+{
+ PRFileDesc *fd;
+ fd = PR_OpenUDPSocket(remoteAddress->raw.family);
+ if (!fd)
+ return false;
+
+ PRNetAddr prRemoteAddress;
+ NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
+ if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PRNetAddr localName;
+ if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PR_Close(fd);
+
+ char dottedDecimal[128];
+ if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS)
+ return false;
+
+ localAddress.Assign(dottedDecimal);
+
+ return true;
+}
+
+// hostName is run through a dns lookup and then a udp socket is connected
+// to the result. If that all works, the local IP address of the socket is
+// returned to the javascript caller and |*aResult| is set to true. Otherwise
+// |*aResult| is set to false.
+bool
+ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName,
+ unsigned int timeout,
+ const JS::CallArgs &aArgs,
+ bool* aResult)
+{
+ *aResult = false;
+
+ NetAddr remoteAddress;
+ nsAutoCString localDottedDecimal;
+ JSContext *cx = mJSContext->Context();
+
+ if (PACResolve(hostName, &remoteAddress, timeout) &&
+ SrcAddress(&remoteAddress, localDottedDecimal)) {
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ *aResult = true;
+ aArgs.rval().setString(dottedDecimalString);
+ }
+ return true;
+}
+
+bool
+ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs)
+{
+ nsAutoCString remoteDottedDecimal;
+ nsAutoCString localDottedDecimal;
+ JSContext *cx = mJSContext->Context();
+ JS::RootedValue v(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ bool useMultihomedDNS =
+ JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
+ !v.isUndefined() && ToBoolean(v);
+
+ // first, lookup the local address of a socket connected
+ // to the host of uri being resolved by the pac file. This is
+ // v6 safe.. but is the last step like that
+ bool rvalAssigned = false;
+ if (useMultihomedDNS) {
+ if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+ } else {
+ // we can still do the fancy multi homing thing if the host is a literal
+ PRNetAddr tempAddr;
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+ if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) &&
+ (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned)) {
+ return rvalAssigned;
+ }
+ }
+
+ // next, look for a route to a public internet address that doesn't need DNS.
+ // This is the google anycast dns address, but it doesn't matter if it
+ // remains operable (as we don't contact it) as long as the address stays
+ // in commonly routed IP address space.
+ remoteDottedDecimal.AssignLiteral("8.8.8.8");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // finally, use the old algorithm based on the local hostname
+ nsAutoCString hostName;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ // without multihomedDNS use such a short timeout that we are basically
+ // just looking at the cache for raw dotted decimals
+ uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
+ if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
+ PACResolveToString(hostName, localDottedDecimal, timeout)) {
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+ }
+
+ // next try a couple RFC 1918 variants.. maybe there is a
+ // local route
+ remoteDottedDecimal.AssignLiteral("192.168.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // more RFC 1918
+ remoteDottedDecimal.AssignLiteral("10.0.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // who knows? let's fallback to localhost
+ localDottedDecimal.AssignLiteral("127.0.0.1");
+ JSString *dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h
new file mode 100644
index 000000000..1f7b88ac6
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfig_h__
+#define ProxyAutoConfig_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsITimer;
+namespace JS {
+class CallArgs;
+} // namespace JS
+
+namespace mozilla { namespace net {
+
+class JSContextWrapper;
+union NetAddr;
+
+// The ProxyAutoConfig class is meant to be created and run on a
+// non main thread. It synchronously resolves PAC files by blocking that
+// thread and running nested event loops. GetProxyForURI is not re-entrant.
+
+class ProxyAutoConfig {
+public:
+ ProxyAutoConfig();
+ ~ProxyAutoConfig();
+
+ nsresult Init(const nsCString &aPACURI,
+ const nsCString &aPACScript,
+ bool aIncludePath);
+ void SetThreadLocalIndex(uint32_t index);
+ void Shutdown();
+ void GC();
+ bool MyIPAddress(const JS::CallArgs &aArgs);
+ bool ResolveAddress(const nsCString &aHostName,
+ NetAddr *aNetAddr, unsigned int aTimeout);
+
+ /**
+ * Get the proxy string for the specified URI. The proxy string is
+ * given by the following:
+ *
+ * result = proxy-spec *( proxy-sep proxy-spec )
+ * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port]
+ * direct-type = "DIRECT"
+ * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5"
+ * proxy-sep = ";" LWS
+ * proxy-host = hostname | ipv4-address-literal
+ * proxy-port = <any 16-bit unsigned integer>
+ * LWS = *( SP | HT )
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ *
+ * NOTE: direct-type and proxy-type are case insensitive
+ * NOTE: SOCKS implies SOCKS4
+ *
+ * Examples:
+ * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ * "SOCKS socksproxy"
+ * "DIRECT"
+ *
+ * XXX add support for IPv6 address literals.
+ * XXX quote whatever the official standard is for PAC.
+ *
+ * @param aTestURI
+ * The URI as an ASCII string to test.
+ * @param aTestHost
+ * The ASCII hostname to test.
+ *
+ * @param result
+ * result string as defined above.
+ */
+ nsresult GetProxyForURI(const nsCString &aTestURI,
+ const nsCString &aTestHost,
+ nsACString &result);
+
+private:
+ // allow 665ms for myipaddress dns queries. That's 95th percentile.
+ const static unsigned int kTimeout = 665;
+
+ // used to compile the PAC file and setup the execution context
+ nsresult SetupJS();
+
+ bool SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress);
+ bool MyIPAddressTryHost(const nsCString &hostName, unsigned int timeout,
+ const JS::CallArgs &aArgs, bool* aResult);
+
+ JSContextWrapper *mJSContext;
+ bool mJSNeedsSetup;
+ bool mShutdown;
+ nsCString mPACScript;
+ nsCString mPACURI;
+ bool mIncludePath;
+ nsCString mRunningHost;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfig_h__
diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp
new file mode 100644
index 000000000..17e26603a
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RedirectChannelRegistrar.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
+
+RedirectChannelRegistrar::RedirectChannelRegistrar()
+ : mRealChannels(32)
+ , mParentChannels(32)
+ , mId(1)
+ , mLock("RedirectChannelRegistrar")
+{
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::RegisterChannel(nsIChannel *channel,
+ uint32_t *_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Put(mId, channel);
+ *_retval = mId;
+
+ ++mId;
+
+ // Ensure we always provide positive ids
+ if (!mId)
+ mId = 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetRegisteredChannel(uint32_t id,
+ nsIChannel **_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::LinkChannels(uint32_t id,
+ nsIParentChannel *channel,
+ nsIChannel** _retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mParentChannels.Put(id, channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetParentChannel(uint32_t id,
+ nsIParentChannel **_retval)
+{
+ MutexAutoLock lock(mLock);
+
+ if (!mParentChannels.Get(id, _retval))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::DeregisterChannels(uint32_t id)
+{
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Remove(id);
+ mParentChannels.Remove(id);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h
new file mode 100644
index 000000000..46e46c264
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RedirectChannelRegistrar_h__
+#define RedirectChannelRegistrar_h__
+
+#include "nsIRedirectChannelRegistrar.h"
+
+#include "nsIChannel.h"
+#include "nsIParentChannel.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREDIRECTCHANNELREGISTRAR
+
+ RedirectChannelRegistrar();
+
+private:
+ ~RedirectChannelRegistrar() {}
+
+protected:
+ typedef nsInterfaceHashtable<nsUint32HashKey, nsIChannel>
+ ChannelHashtable;
+ typedef nsInterfaceHashtable<nsUint32HashKey, nsIParentChannel>
+ ParentChannelHashtable;
+
+ ChannelHashtable mRealChannels;
+ ParentChannelHashtable mParentChannels;
+ uint32_t mId;
+ Mutex mLock;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h
new file mode 100644
index 000000000..591b9daf0
--- /dev/null
+++ b/netwerk/base/ReferrerPolicy.h
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ReferrerPolicy_h__
+#define ReferrerPolicy_h__
+
+#include "nsStringGlue.h"
+#include "nsIHttpChannel.h"
+#include "nsUnicharUtils.h"
+
+namespace mozilla { namespace net {
+
+enum ReferrerPolicy {
+ /* spec tokens: never no-referrer */
+ RP_No_Referrer = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER,
+
+ /* spec tokens: origin */
+ RP_Origin = nsIHttpChannel::REFERRER_POLICY_ORIGIN,
+
+ /* spec tokens: default no-referrer-when-downgrade */
+ RP_No_Referrer_When_Downgrade = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
+ RP_Default = nsIHttpChannel::REFERRER_POLICY_DEFAULT,
+
+ /* spec tokens: origin-when-cross-origin */
+ RP_Origin_When_Crossorigin = nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN,
+
+ /* spec tokens: always unsafe-url */
+ RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL,
+
+ /* spec tokens: same-origin */
+ RP_Same_Origin = nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN,
+
+ /* spec tokens: strict-origin */
+ RP_Strict_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN,
+
+ /* spec tokens: strict-origin-when-cross-origin */
+ RP_Strict_Origin_When_Cross_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN,
+
+ /* spec tokens: empty string */
+ /* The empty string "" corresponds to no referrer policy, or unset policy */
+ RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET,
+};
+
+/* spec tokens: never no-referrer */
+const char kRPS_Never[] = "never";
+const char kRPS_No_Referrer[] = "no-referrer";
+
+/* spec tokens: origin */
+const char kRPS_Origin[] = "origin";
+
+/* spec tokens: default no-referrer-when-downgrade */
+const char kRPS_Default[] = "default";
+const char kRPS_No_Referrer_When_Downgrade[] = "no-referrer-when-downgrade";
+
+/* spec tokens: origin-when-cross-origin */
+const char kRPS_Origin_When_Cross_Origin[] = "origin-when-cross-origin";
+const char kRPS_Origin_When_Crossorigin[] = "origin-when-crossorigin";
+
+/* spec tokens: same-origin */
+const char kRPS_Same_Origin[] = "same-origin";
+
+/* spec tokens: strict-origin */
+const char kRPS_Strict_Origin[] = "strict-origin";
+
+/* spec tokens: strict-origin-when-cross-origin */
+const char kRPS_Strict_Origin_When_Cross_Origin[] = "strict-origin-when-cross-origin";
+
+/* spec tokens: always unsafe-url */
+const char kRPS_Always[] = "always";
+const char kRPS_Unsafe_URL[] = "unsafe-url";
+
+inline ReferrerPolicy
+ReferrerPolicyFromString(const nsAString& content)
+{
+ if (content.IsEmpty()) {
+ return RP_No_Referrer;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+ // This is implemented step by step as described in the Referrer Policy
+ // specification, section "Determine token's Policy".
+ if (lowerContent.EqualsLiteral(kRPS_Never) ||
+ lowerContent.EqualsLiteral(kRPS_No_Referrer)) {
+ return RP_No_Referrer;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin)) {
+ return RP_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Default) ||
+ lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) {
+ return RP_No_Referrer_When_Downgrade;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) ||
+ lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)) {
+ return RP_Origin_When_Crossorigin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) {
+ return RP_Same_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) {
+ return RP_Strict_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) {
+ return RP_Strict_Origin_When_Cross_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Always) ||
+ lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) {
+ return RP_Unsafe_URL;
+ }
+ // Spec says if none of the previous match, use empty string.
+ return RP_Unset;
+
+}
+
+inline bool
+IsValidReferrerPolicy(const nsAString& content)
+{
+ if (content.IsEmpty()) {
+ return true;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+
+ return lowerContent.EqualsLiteral(kRPS_Never)
+ || lowerContent.EqualsLiteral(kRPS_No_Referrer)
+ || lowerContent.EqualsLiteral(kRPS_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Default)
+ || lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)
+ || lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)
+ || lowerContent.EqualsLiteral(kRPS_Same_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Strict_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)
+ || lowerContent.EqualsLiteral(kRPS_Always)
+ || lowerContent.EqualsLiteral(kRPS_Unsafe_URL);
+}
+
+inline ReferrerPolicy
+AttributeReferrerPolicyFromString(const nsAString& content)
+{
+ // Specs : https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute
+ // Spec says the empty string "" corresponds to no referrer policy, or RP_Unset
+ if (content.IsEmpty()) {
+ return RP_Unset;
+ }
+
+ nsString lowerContent(content);
+ ToLowerCase(lowerContent);
+
+ if (lowerContent.EqualsLiteral(kRPS_No_Referrer)) {
+ return RP_No_Referrer;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin)) {
+ return RP_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) {
+ return RP_No_Referrer_When_Downgrade;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)) {
+ return RP_Origin_When_Crossorigin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) {
+ return RP_Unsafe_URL;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) {
+ return RP_Strict_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) {
+ return RP_Same_Origin;
+ }
+ if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) {
+ return RP_Strict_Origin_When_Cross_Origin;
+ }
+
+ // Spec says invalid value default is empty string state
+ // So, return RP_Unset if none of the previous match, return RP_Unset
+ return RP_Unset;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp
new file mode 100644
index 000000000..b72e42b13
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "RequestContextService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Services.h"
+
+#include "mozilla/net/PSpdyPush.h"
+
+namespace mozilla {
+namespace net {
+
+// nsIRequestContext
+class RequestContext final : public nsIRequestContext
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+
+ explicit RequestContext(const nsID& id);
+private:
+ virtual ~RequestContext();
+
+ nsID mID;
+ char mCID[NSID_LENGTH];
+ Atomic<uint32_t> mBlockingTransactionCount;
+ nsAutoPtr<SpdyPushCache> mSpdyCache;
+ nsCString mUserAgentOverride;
+};
+
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext)
+
+RequestContext::RequestContext(const nsID& aID)
+ : mBlockingTransactionCount(0)
+{
+ mID = aID;
+ mID.ToProvidedString(mCID);
+}
+
+RequestContext::~RequestContext()
+{
+}
+
+NS_IMETHODIMP
+RequestContext::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount)
+{
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::AddBlockingTransaction()
+{
+ mBlockingTransactionCount++;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveBlockingTransaction(uint32_t *outval)
+{
+ NS_ENSURE_ARG_POINTER(outval);
+ mBlockingTransactionCount--;
+ *outval = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache)
+{
+ *aSpdyPushCache = mSpdyCache.get();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache)
+{
+ mSpdyCache = aSpdyPushCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetID(nsID *outval)
+{
+ NS_ENSURE_ARG_POINTER(outval);
+ *outval = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetUserAgentOverride(nsACString& aUserAgentOverride)
+{
+ aUserAgentOverride = mUserAgentOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::SetUserAgentOverride(const nsACString& aUserAgentOverride)
+{
+ mUserAgentOverride = aUserAgentOverride;
+ return NS_OK;
+}
+
+
+//nsIRequestContextService
+RequestContextService *RequestContextService::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
+
+RequestContextService::RequestContextService()
+{
+ MOZ_ASSERT(!sSelf, "multiple rcs instances!");
+ MOZ_ASSERT(NS_IsMainThread());
+ sSelf = this;
+}
+
+RequestContextService::~RequestContextService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+ sSelf = nullptr;
+}
+
+nsresult
+RequestContextService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+
+void
+RequestContextService::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Clear();
+}
+
+/* static */ nsresult
+RequestContextService::Create(nsISupports *aOuter, const nsIID& aIID, void **aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<RequestContextService> svc = new RequestContextService();
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return svc->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContext(const nsID& rcID, nsIRequestContext **rc)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (!mTable.Get(rcID, rc)) {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.Put(rcID, newSC);
+ newSC.swap(*rc);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::NewRequestContextID(nsID *rcID)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mUUIDGen) {
+ nsresult rv;
+ mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mUUIDGen->GenerateUUIDInPlace(rcID);
+}
+
+NS_IMETHODIMP
+RequestContextService::RemoveRequestContext(const nsID& rcID)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Remove(rcID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data_unicode)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+} // ::mozilla::net
+} // ::mozilla
diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h
new file mode 100644
index 000000000..d7b8e971a
--- /dev/null
+++ b/netwerk/base/RequestContextService.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__RequestContextService_h
+#define mozilla__net__RequestContextService_h
+
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIObserver.h"
+#include "nsIRequestContext.h"
+
+class nsIUUIDGenerator;
+
+namespace mozilla {
+namespace net {
+
+class RequestContextService final
+ : public nsIRequestContextService
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXTSERVICE
+ NS_DECL_NSIOBSERVER
+
+ RequestContextService();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+private:
+ virtual ~RequestContextService();
+
+ static RequestContextService *sSelf;
+
+ nsInterfaceHashtable<nsIDHashKey, nsIRequestContext> mTable;
+ nsCOMPtr<nsIUUIDGenerator> mUUIDGen;
+};
+
+} // ::mozilla::net
+} // ::mozilla
+
+#endif // mozilla__net__RequestContextService_h
diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp
new file mode 100644
index 000000000..65d792459
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "ShutdownLayer.h"
+#include "prerror.h"
+#include "private/pprio.h"
+#include "prmem.h"
+#include <winsock2.h>
+
+static PRDescIdentity sWinSockShutdownLayerIdentity;
+static PRIOMethods sWinSockShutdownLayerMethods;
+static PRIOMethods *sWinSockShutdownLayerMethodsPtr = nullptr;
+
+namespace mozilla {
+namespace net {
+
+extern PRDescIdentity nsNamedPipeLayerIdentity;
+
+} // namespace net
+} // namespace mozilla
+
+
+PRStatus
+WinSockClose(PRFileDesc *aFd)
+{
+ MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity,
+ "Windows shutdown layer not on the top of the stack");
+
+ PROsfd osfd = PR_FileDesc2NativeHandle(aFd);
+ if (osfd != -1) {
+ shutdown(osfd, SD_BOTH);
+ }
+
+ aFd->identity = PR_INVALID_IO_LAYER;
+
+ if (aFd->lower) {
+ return aFd->lower->methods->close(aFd->lower);
+ } else {
+ return PR_SUCCESS;
+ }
+}
+
+nsresult mozilla::net::AttachShutdownLayer(PRFileDesc *aFd)
+{
+ if (!sWinSockShutdownLayerMethodsPtr) {
+ sWinSockShutdownLayerIdentity =
+ PR_GetUniqueIdentity("windows shutdown call layer");
+ sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods();
+ sWinSockShutdownLayerMethods.close = WinSockClose;
+ sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods;
+ }
+
+ if (mozilla::net::nsNamedPipeLayerIdentity &&
+ PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) {
+ // Do not attach shutdown layer on named pipe layer,
+ // it is for PR_NSPR_IO_LAYER only.
+ return NS_OK;
+ }
+
+ PRFileDesc * layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity,
+ sWinSockShutdownLayerMethodsPtr);
+ if (!layer) {
+ return NS_OK;
+ }
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_DELETE(layer);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h
new file mode 100644
index 000000000..59843b7d0
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SHUTDOWNLAYER_H___
+#define SHUTDOWNLAYER_H___
+
+#include "nscore.h"
+#include "prio.h"
+
+namespace mozilla { namespace net {
+
+// This is only for windows. This layer will be attached jus before PR_CLose
+// is call and it will only call shutdown(sock).
+extern nsresult AttachShutdownLayer(PRFileDesc *fd);
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SHUTDOWN_H___ */
diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp
new file mode 100644
index 000000000..d0e311f77
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleBuffer.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+SimpleBuffer::SimpleBuffer()
+ : mStatus(NS_OK)
+ , mAvailable(0)
+{
+ mOwningThread = PR_GetCurrentThread();
+}
+
+nsresult SimpleBuffer::Write(char *src, size_t len)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ while (len > 0) {
+ SimpleBufferPage *p = mBufferList.getLast();
+ if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) {
+ // no room.. make a new page
+ p = nullptr;
+ }
+ if (!p) {
+ p = new (fallible) SimpleBufferPage();
+ if (!p) {
+ mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return mStatus;
+ }
+ mBufferList.insertBack(p);
+ }
+ size_t roomOnPage = SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset;
+ size_t toWrite = std::min(roomOnPage, len);
+ memcpy(p->mBuffer + p->mWriteOffset, src, toWrite);
+ src += toWrite;
+ len -= toWrite;
+ p->mWriteOffset += toWrite;
+ mAvailable += toWrite;
+ }
+ return NS_OK;
+}
+
+size_t SimpleBuffer::Read(char *dest, size_t maxLen)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ if (NS_FAILED(mStatus)) {
+ return 0;
+ }
+
+ size_t rv = 0;
+ for (SimpleBufferPage *p = mBufferList.getFirst();
+ p && (rv < maxLen); p = mBufferList.getFirst()) {
+ size_t avail = p->mWriteOffset - p->mReadOffset;
+ size_t toRead = std::min(avail, (maxLen - rv));
+ memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead);
+ rv += toRead;
+ p->mReadOffset += toRead;
+ if (p->mReadOffset == p->mWriteOffset) {
+ p->remove();
+ delete p;
+ }
+ }
+
+ MOZ_ASSERT(mAvailable >= rv);
+ mAvailable -= rv;
+ return rv;
+}
+
+size_t SimpleBuffer::Available()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ return NS_SUCCEEDED(mStatus) ? mAvailable : 0;
+}
+
+void SimpleBuffer::Clear()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+ SimpleBufferPage *p;
+ while ((p = mBufferList.popFirst())) {
+ delete p;
+ }
+ mAvailable = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h
new file mode 100644
index 000000000..765239001
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleBuffer_h__
+#define SimpleBuffer_h__
+
+/*
+ This class is similar to a nsPipe except it does not have any locking, stores
+ an unbounded amount of data, can only be used on one thread, and has much
+ simpler result code semantics to deal with.
+*/
+
+#include "prtypes.h"
+#include "mozilla/LinkedList.h"
+#include "nsIThreadInternal.h"
+
+namespace mozilla {
+namespace net {
+
+class SimpleBufferPage : public LinkedListElement<SimpleBufferPage>
+{
+public:
+ SimpleBufferPage() : mReadOffset(0), mWriteOffset(0) {}
+ static const size_t kSimpleBufferPageSize = 32000;
+
+private:
+ friend class SimpleBuffer;
+ char mBuffer[kSimpleBufferPageSize];
+ size_t mReadOffset;
+ size_t mWriteOffset;
+};
+
+class SimpleBuffer
+{
+public:
+ SimpleBuffer();
+ ~SimpleBuffer() {}
+
+ nsresult Write(char *stc, size_t len); // return OK or OUT_OF_MEMORY
+ size_t Read(char *dest, size_t maxLen); // return bytes read
+ size_t Available();
+ void Clear();
+
+private:
+ PRThread *mOwningThread;
+ nsresult mStatus;
+ AutoCleanLinkedList<SimpleBufferPage> mBufferList;
+ size_t mAvailable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/StreamingProtocolService.cpp b/netwerk/base/StreamingProtocolService.cpp
new file mode 100644
index 000000000..3df3326bc
--- /dev/null
+++ b/netwerk/base/StreamingProtocolService.cpp
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "StreamingProtocolService.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIURI.h"
+#include "necko-config.h"
+
+#ifdef NECKO_PROTOCOL_rtsp
+#include "RtspControllerChild.h"
+#include "RtspController.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(StreamingProtocolControllerService,
+ nsIStreamingProtocolControllerService)
+
+/* static */
+StaticRefPtr<StreamingProtocolControllerService> sSingleton;
+
+/* static */
+already_AddRefed<StreamingProtocolControllerService>
+StreamingProtocolControllerService::GetInstance()
+{
+ if (!sSingleton) {
+ sSingleton = new StreamingProtocolControllerService();
+ ClearOnShutdown(&sSingleton);
+ }
+ RefPtr<StreamingProtocolControllerService> service = sSingleton.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP
+StreamingProtocolControllerService::Create(nsIChannel *aChannel, nsIStreamingProtocolController **aResult)
+{
+ RefPtr<nsIStreamingProtocolController> mediacontroller;
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString scheme;
+
+ NS_ENSURE_ARG_POINTER(aChannel);
+ aChannel->GetURI(getter_AddRefs(uri));
+
+ nsresult rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef NECKO_PROTOCOL_rtsp
+ if (scheme.EqualsLiteral("rtsp")) {
+ if (IsNeckoChild()) {
+ mediacontroller = new RtspControllerChild(aChannel);
+ } else {
+ mediacontroller = new RtspController(aChannel);
+ }
+ }
+#endif
+
+ if (!mediacontroller) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ mediacontroller->Init(uri);
+
+ mediacontroller.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/StreamingProtocolService.h b/netwerk/base/StreamingProtocolService.h
new file mode 100644
index 000000000..96d51fe5c
--- /dev/null
+++ b/netwerk/base/StreamingProtocolService.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_StreamingProtocolControllerService_h
+#define mozilla_net_StreamingProtocolControllerService_h
+
+#include "mozilla/StaticPtr.h"
+#include "nsIStreamingProtocolService.h"
+#include "nsIStreamingProtocolController.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class implements a service to help to create streaming protocol controller.
+ */
+class StreamingProtocolControllerService : public nsIStreamingProtocolControllerService
+{
+private:
+ virtual ~StreamingProtocolControllerService() {};
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMINGPROTOCOLCONTROLLERSERVICE
+
+ StreamingProtocolControllerService() {};
+ static already_AddRefed<StreamingProtocolControllerService> GetInstance();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif //mozilla_net_StreamingProtocolControllerService_h
diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp
new file mode 100644
index 000000000..b32a9a188
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.cpp
@@ -0,0 +1,515 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TLSServerSocket.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIServerSocket.h"
+#include "nsITimer.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket
+//-----------------------------------------------------------------------------
+
+TLSServerSocket::TLSServerSocket()
+ : mServerCert(nullptr)
+{
+}
+
+TLSServerSocket::~TLSServerSocket()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket,
+ nsServerSocket,
+ nsITLSServerSocket)
+
+nsresult
+TLSServerSocket::SetSocketDefaults()
+{
+ // Set TLS options on the listening socket
+ mFD = SSL_ImportFD(nullptr, mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSL_OptionSet(mFD, SSL_SECURITY, true);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true);
+
+ // We don't currently notify the server API consumer of renegotiation events
+ // (to revalidate peer certs, etc.), so disable it for now.
+ SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+
+ SetSessionCache(true);
+ SetSessionTickets(true);
+ SetRequestClientCertificate(REQUEST_NEVER);
+
+ return NS_OK;
+}
+
+void
+TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsresult rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ RefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo();
+ info->mServerSocket = this;
+ info->mTransport = trans;
+ nsCOMPtr<nsISupports> infoSupports =
+ NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info);
+ rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ // Override the default peer certificate validation, so that server consumers
+ // can make their own choice after the handshake completes.
+ SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+ // Once the TLS handshake has completed, the server consumer is notified and
+ // has access to various TLS state details.
+ // It's safe to pass info here because the socket transport holds it as
+ // |mSecInfo| which keeps it alive for the lifetime of the socket.
+ SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+ info);
+
+ // Notify the consumer of the new client so it can manage the streams.
+ // Security details aren't known yet. The security observer will be notified
+ // later when they are ready.
+ nsCOMPtr<nsIServerSocket> serverSocket =
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+ mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult
+TLSServerSocket::OnSocketListen()
+{
+ if (NS_WARN_IF(!mServerCert)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ UniqueCERTCertificate cert(mServerCert->GetCert());
+ if (NS_WARN_IF(!cert)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (NS_WARN_IF(!key)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+
+ nsresult rv = MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(),
+ certKEA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+SECStatus
+TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig,
+ PRBool isServer)
+{
+ // Allow any client cert here, server consumer code can decide whether it's
+ // okay after being notified of the new client socket.
+ return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert)
+{
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mServerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ mServerCert = aCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionCache(bool aEnabled)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_NO_CACHE, !aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionTickets(bool aEnabled)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetRequestClientCertificate(uint32_t aMode)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER);
+
+ switch (aMode) {
+ case REQUEST_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR);
+ break;
+ case REQUIRE_FIRST_HANDSHAKE:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE);
+ break;
+ case REQUIRE_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
+ break;
+ default:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetCipherSuites(uint16_t* aCipherSuites, uint32_t aLength)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
+ uint16_t cipher_id = SSL_ImplementedCiphers[i];
+ if (SSL_CipherPrefSet(mFD, cipher_id, false) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+ }
+
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (SSL_CipherPrefSet(mFD, aCipherSuites[i], true) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion)
+{
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ SSLVersionRange range = {aMinVersion, aMaxVersion};
+ if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerConnectionInfo
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TLSServerSecurityObserverProxy final : public nsITLSServerSecurityObserver
+{
+ ~TLSServerSecurityObserverProxy() {}
+
+public:
+ explicit TLSServerSecurityObserverProxy(nsITLSServerSecurityObserver* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(aListener))
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+ class OnHandshakeDoneRunnable : public Runnable
+ {
+ public:
+ OnHandshakeDoneRunnable(const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener,
+ nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus)
+ : mListener(aListener)
+ , mServer(aServer)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+ nsCOMPtr<nsITLSServerSocket> mServer;
+ nsCOMPtr<nsITLSClientStatus> mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+};
+
+NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy,
+ nsITLSServerSecurityObserver)
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus)
+{
+ RefPtr<OnHandshakeDoneRunnable> r =
+ new OnHandshakeDoneRunnable(mListener, aServer, aStatus);
+ return NS_DispatchToMainThread(r);
+}
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run()
+{
+ mListener->OnHandshakeDone(mServer, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(TLSServerConnectionInfo,
+ nsITLSServerConnectionInfo,
+ nsITLSClientStatus)
+
+TLSServerConnectionInfo::TLSServerConnectionInfo()
+ : mServerSocket(nullptr)
+ , mTransport(nullptr)
+ , mPeerCert(nullptr)
+ , mTlsVersionUsed(TLS_VERSION_UNKNOWN)
+ , mKeyLength(0)
+ , mMacLength(0)
+ , mLock("TLSServerConnectionInfo.mLock")
+ , mSecurityObserver(nullptr)
+{
+}
+
+TLSServerConnectionInfo::~TLSServerConnectionInfo()
+{
+ if (!mSecurityObserver) {
+ return;
+ }
+
+ RefPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ observer = mSecurityObserver.forget();
+ }
+
+ if (observer) {
+ NS_ReleaseOnMainThread(observer.forget());
+ }
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::SetSecurityObserver(nsITLSServerSecurityObserver* aObserver)
+{
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket)
+{
+ if (NS_WARN_IF(!aSocket)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aSocket = mServerSocket;
+ NS_IF_ADDREF(*aSocket);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus)
+{
+ if (NS_WARN_IF(!aStatus)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aStatus = this;
+ NS_IF_ADDREF(*aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert)
+{
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mPeerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed)
+{
+ if (NS_WARN_IF(!aTlsVersionUsed)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aTlsVersionUsed = mTlsVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName)
+{
+ aCipherName.Assign(mCipherName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength)
+{
+ if (NS_WARN_IF(!aKeyLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aKeyLength = mKeyLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength)
+{
+ if (NS_WARN_IF(!aMacLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aMacLength = mMacLength;
+ return NS_OK;
+}
+
+// static
+void
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg)
+{
+ RefPtr<TLSServerConnectionInfo> info =
+ static_cast<TLSServerConnectionInfo*>(aArg);
+ nsISocketTransport* transport = info->mTransport;
+ // No longer needed outside this function, so clear the weak ref
+ info->mTransport = nullptr;
+ nsresult rv = info->HandshakeCallback(aFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ transport->Close(rv);
+ }
+}
+
+nsresult
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD)
+{
+ nsresult rv;
+
+ UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+ if (clientCert) {
+ nsCOMPtr<nsIX509CertDB> certDB =
+ do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> clientCertPSM;
+ rv = certDB->ConstructX509(reinterpret_cast<char*>(clientCert->derCert.data),
+ clientCert->derCert.len,
+ getter_AddRefs(clientCertPSM));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPeerCert = clientCertPSM;
+ }
+
+ SSLChannelInfo channelInfo;
+ rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mTlsVersionUsed = channelInfo.protocolVersion;
+
+ SSLCipherSuiteInfo cipherInfo;
+ rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCipherName.Assign(cipherInfo.cipherSuiteName);
+ mKeyLength = cipherInfo.effectiveKeyBits;
+ mMacLength = cipherInfo.macBits;
+
+ if (!mSecurityObserver) {
+ return NS_OK;
+ }
+
+ // Notify consumer code that handshake is complete
+ nsCOMPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityObserver.swap(observer);
+ }
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ observer->OnHandshakeDone(serverSocket, this);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h
new file mode 100644
index 000000000..9fb57e0cc
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.h
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TLSServerSocket_h
+#define mozilla_net_TLSServerSocket_h
+
+#include "nsAutoPtr.h"
+#include "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket final : public nsServerSocket
+ , public nsITLSServerSocket
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+ NS_DECL_NSITLSSERVERSOCKET
+
+ // Override methods from nsServerSocket
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const NetAddr& clientAddr) override;
+ virtual nsresult SetSocketDefaults() override;
+ virtual nsresult OnSocketListen() override;
+
+ TLSServerSocket();
+
+private:
+ virtual ~TLSServerSocket();
+
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+
+ nsCOMPtr<nsIX509Cert> mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo
+ , public nsITLSClientStatus
+{
+ friend class TLSServerSocket;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERCONNECTIONINFO
+ NS_DECL_NSITLSCLIENTSTATUS
+
+ TLSServerConnectionInfo();
+
+private:
+ virtual ~TLSServerConnectionInfo();
+
+ static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+ nsresult HandshakeCallback(PRFileDesc* aFD);
+
+ RefPtr<TLSServerSocket> mServerSocket;
+ // Weak ref to the transport, to avoid cycles since the transport holds a
+ // reference to the TLSServerConnectionInfo object. This is not handed out to
+ // anyone, and is only used in HandshakeCallback to close the transport in
+ // case of an error. After this, it's set to nullptr.
+ nsISocketTransport* mTransport;
+ nsCOMPtr<nsIX509Cert> mPeerCert;
+ int16_t mTlsVersionUsed;
+ nsCString mCipherName;
+ uint32_t mKeyLength;
+ uint32_t mMacLength;
+ // lock protects access to mSecurityObserver
+ mozilla::Mutex mLock;
+ nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp
new file mode 100644
index 000000000..d5b8a41df
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ThrottleQueue.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsStreamUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class ThrottleInputStream final
+ : public nsIAsyncInputStream
+ , public nsISeekableStream
+{
+public:
+
+ ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void AllowInput();
+
+private:
+
+ ~ThrottleInputStream();
+
+ nsCOMPtr<nsIInputStream> mStream;
+ RefPtr<ThrottleQueue> mQueue;
+ nsresult mClosedStatus;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, nsISeekableStream)
+
+ThrottleInputStream::ThrottleInputStream(nsIInputStream *aStream, ThrottleQueue* aQueue)
+ : mStream(aStream)
+ , mQueue(aQueue)
+ , mClosedStatus(NS_OK)
+{
+ MOZ_ASSERT(aQueue != nullptr);
+}
+
+ThrottleInputStream::~ThrottleInputStream()
+{
+ Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Close()
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ if (mQueue) {
+ mQueue->DequeueStream(this);
+ mQueue = nullptr;
+ mClosedStatus = NS_BASE_STREAM_CLOSED;
+ }
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Available(uint64_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->Read(aBuf, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Tell(int64_t* aResult)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Tell(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::SetEOF()
+{
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->SetEOF();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::CloseWithStatus(nsresult aStatus)
+{
+ if (NS_FAILED(mClosedStatus)) {
+ // Already closed, ignore.
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ mClosedStatus = Close();
+ if (NS_SUCCEEDED(mClosedStatus)) {
+ mClosedStatus = aStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ if (aFlags != 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mCallback = aCallback;
+ mEventTarget = aEventTarget;
+ if (mCallback) {
+ mQueue->QueueStream(this);
+ } else {
+ mQueue->DequeueStream(this);
+ }
+ return NS_OK;
+}
+
+void
+ThrottleInputStream::AllowInput()
+{
+ MOZ_ASSERT(mCallback);
+ nsCOMPtr<nsIInputStreamCallback> callbackEvent =
+ NS_NewInputStreamReadyEvent(mCallback, mEventTarget);
+ mCallback = nullptr;
+ mEventTarget = nullptr;
+ callbackEvent->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback)
+
+ThrottleQueue::ThrottleQueue()
+ : mMeanBytesPerSecond(0)
+ , mMaxBytesPerSecond(0)
+ , mBytesProcessed(0)
+ , mTimerArmed(false)
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer)
+ mTimer->SetTarget(sts);
+}
+
+ThrottleQueue::~ThrottleQueue()
+{
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::RecordRead(uint32_t aBytesRead)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ ThrottleEntry entry;
+ entry.mTime = TimeStamp::Now();
+ entry.mBytesRead = aBytesRead;
+ mReadEvents.AppendElement(entry);
+ mBytesProcessed += aBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1);
+ size_t i;
+
+ // Remove all stale events.
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ if (mReadEvents[i].mTime >= oneSecondAgo) {
+ break;
+ }
+ }
+ mReadEvents.RemoveElementsAt(0, i);
+
+ uint32_t totalBytes = 0;
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ totalBytes += mReadEvents[i].mBytesRead;
+ }
+
+ uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond;
+ double prob = static_cast<double>(rand()) / RAND_MAX;
+ uint32_t thisSliceBytes = mMeanBytesPerSecond - spread +
+ static_cast<uint32_t>(2 * spread * prob);
+
+ if (totalBytes >= thisSliceBytes) {
+ *aAvailable = 0;
+ } else {
+ *aAvailable = thisSliceBytes;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond)
+{
+ // Can be called on any thread.
+ if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || aMaxBytesPerSecond < aMeanBytesPerSecond) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mMeanBytesPerSecond = aMeanBytesPerSecond;
+ mMaxBytesPerSecond = aMaxBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::BytesProcessed(uint64_t* aResult)
+{
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::WrapStream(nsIInputStream* aInputStream, nsIAsyncInputStream** aResult)
+{
+ nsCOMPtr<nsIAsyncInputStream> result = new ThrottleInputStream(aInputStream, this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Notify(nsITimer* aTimer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ // A notified reader may need to push itself back on the queue.
+ // Swap out the list of readers so that this works properly.
+ nsTArray<RefPtr<ThrottleInputStream>> events;
+ events.SwapElements(mAsyncEvents);
+
+ // Optimistically notify all the waiting readers, and then let them
+ // requeue if there isn't enough bandwidth.
+ for (size_t i = 0; i < events.Length(); ++i) {
+ events[i]->AllowInput();
+ }
+
+ mTimerArmed = false;
+ return NS_OK;
+}
+
+void
+ThrottleQueue::QueueStream(ThrottleInputStream* aStream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (mAsyncEvents.IndexOf(aStream) == mAsyncEvents.NoIndex) {
+ mAsyncEvents.AppendElement(aStream);
+
+ if (!mTimerArmed) {
+ uint32_t ms = 1000;
+ if (mReadEvents.Length() > 0) {
+ TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
+ TimeStamp now = TimeStamp::Now();
+
+ if (t > now) {
+ ms = static_cast<uint32_t>((t - now).ToMilliseconds());
+ } else {
+ ms = 1;
+ }
+ }
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
+ mTimerArmed = true;
+ }
+ }
+ }
+}
+
+void
+ThrottleQueue::DequeueStream(ThrottleInputStream* aStream)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ mAsyncEvents.RemoveElement(aStream);
+}
+
+}
+}
diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h
new file mode 100644
index 000000000..5e16c8ef6
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ThrottleQueue_h
+#define mozilla_net_ThrottleQueue_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace net {
+
+class ThrottleInputStream;
+
+/**
+ * An implementation of nsIInputChannelThrottleQueue that can be used
+ * to throttle uploads. This class is not thread-safe.
+ * Initialization and calls to WrapStream may be done on any thread;
+ * but otherwise, after creation, it can only be used on the socket
+ * thread. It currently throttles with a one second granularity, so
+ * may be a bit choppy.
+ */
+
+class ThrottleQueue final
+ : public nsIInputChannelThrottleQueue
+ , public nsITimerCallback
+{
+public:
+
+ ThrottleQueue();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
+ NS_DECL_NSITIMERCALLBACK
+
+ void QueueStream(ThrottleInputStream* aStream);
+ void DequeueStream(ThrottleInputStream* aStream);
+
+private:
+
+ ~ThrottleQueue();
+
+ struct ThrottleEntry {
+ TimeStamp mTime;
+ uint32_t mBytesRead;
+ };
+
+ nsTArray<ThrottleEntry> mReadEvents;
+ uint32_t mMeanBytesPerSecond;
+ uint32_t mMaxBytesPerSecond;
+ uint64_t mBytesProcessed;
+
+ nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed;
+};
+
+}
+}
+
+#endif // mozilla_net_ThrottleQueue_h
diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp
new file mode 100644
index 000000000..555fdbbe5
--- /dev/null
+++ b/netwerk/base/Tickler.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tickler.h"
+
+#ifdef MOZ_USE_WIFI_TICKLER
+#include "nsComponentManagerUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prnetdb.h"
+
+#include "mozilla/jni/Utils.h"
+#include "GeneratedJNIWrappers.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler)
+
+Tickler::Tickler()
+ : mLock("Tickler::mLock")
+ , mActive(false)
+ , mCanceled(false)
+ , mEnabled(false)
+ , mDelay(16)
+ , mDuration(TimeDuration::FromMilliseconds(400))
+ , mFD(nullptr)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Tickler::~Tickler()
+{
+ // non main thread uses of the tickler should hold weak
+ // references to it if they must hold a reference at all
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mThread) {
+ mThread->AsyncShutdown();
+ mThread = nullptr;
+ }
+
+ if (mTimer)
+ mTimer->Cancel();
+ if (mFD)
+ PR_Close(mFD);
+}
+
+nsresult
+Tickler::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mTimer);
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(!mFD);
+
+ if (jni::IsAvailable()) {
+ java::GeckoAppShell::EnableNetworkNotifications();
+ }
+
+ mFD = PR_OpenUDPSocket(PR_AF_INET);
+ if (!mFD)
+ return NS_ERROR_FAILURE;
+
+ // make sure new socket has a ttl of 1
+ // failure is not fatal.
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_IpTimeToLive;
+ opt.value.ip_ttl = 1;
+ PR_SetSocketOption(mFD, &opt);
+
+ nsresult rv = NS_NewNamedThread("wifi tickler",
+ getter_AddRefs(mThread));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsITimer> tmpTimer(do_CreateInstance(NS_TIMER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = tmpTimer->SetTarget(mThread);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mTimer.swap(tmpTimer);
+
+ mAddr.inet.family = PR_AF_INET;
+ mAddr.inet.port = PR_htons (4886);
+ mAddr.inet.ip = 0;
+
+ return NS_OK;
+}
+
+void Tickler::Tickle()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread);
+ mLastTickle = TimeStamp::Now();
+ if (!mActive)
+ MaybeStartTickler();
+}
+
+void Tickler::PostCheckTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ mThread->Dispatch(NewRunnableMethod(this, &Tickler::CheckTickler),
+ NS_DISPATCH_NORMAL);
+ return;
+}
+
+void Tickler::MaybeStartTicklerUnlocked()
+{
+ MutexAutoLock lock(mLock);
+ MaybeStartTickler();
+}
+
+void Tickler::MaybeStartTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod(this, &Tickler::MaybeStartTicklerUnlocked));
+ return;
+ }
+
+ if (!mPrefs)
+ mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (mPrefs) {
+ int32_t val;
+ bool boolVal;
+
+ if (NS_SUCCEEDED(mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal)))
+ mEnabled = boolVal;
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) {
+ if (val < 1)
+ val = 1;
+ if (val > 100000)
+ val = 100000;
+ mDuration = TimeDuration::FromMilliseconds(val);
+ }
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) {
+ if (val < 1)
+ val = 1;
+ if (val > 1000)
+ val = 1000;
+ mDelay = static_cast<uint32_t>(val);
+ }
+ }
+
+ PostCheckTickler();
+}
+
+void Tickler::CheckTickler()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+
+ bool shouldRun = (!mCanceled) &&
+ ((TimeStamp::Now() - mLastTickle) <= mDuration);
+
+ if ((shouldRun && mActive) || (!shouldRun && !mActive))
+ return; // no change in state
+
+ if (mActive)
+ StopTickler();
+ else
+ StartTickler();
+}
+
+void Tickler::Cancel()
+{
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ mCanceled = true;
+ if (mThread)
+ PostCheckTickler();
+}
+
+void Tickler::StopTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(mTimer);
+ MOZ_ASSERT(mActive);
+
+ mTimer->Cancel();
+ mActive = false;
+}
+
+class TicklerTimer final : public nsITimerCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ TicklerTimer(Tickler *aTickler)
+ {
+ mTickler = do_GetWeakReference(aTickler);
+ }
+
+private:
+ ~TicklerTimer() {}
+
+ nsWeakPtr mTickler;
+};
+
+void Tickler::StartTickler()
+{
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(mTimer);
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this),
+ mEnabled ? mDelay : 1000,
+ nsITimer::TYPE_REPEATING_SLACK)))
+ mActive = true;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Address(uint32_t address)
+{
+ mAddr.inet.ip = address;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Port(uint16_t port)
+{
+ mAddr.inet.port = port;
+}
+
+NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback)
+
+NS_IMETHODIMP TicklerTimer::Notify(nsITimer *timer)
+{
+ RefPtr<Tickler> tickler = do_QueryReferent(mTickler);
+ if (!tickler)
+ return NS_ERROR_FAILURE;
+ MutexAutoLock lock(tickler->mLock);
+
+ if (!tickler->mFD) {
+ tickler->StopTickler();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (tickler->mCanceled ||
+ ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) {
+ tickler->StopTickler();
+ return NS_OK;
+ }
+
+ if (!tickler->mEnabled)
+ return NS_OK;
+
+ PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+namespace mozilla {
+namespace net {
+NS_IMPL_ISUPPORTS0(Tickler)
+} // namespace net
+} // namespace mozilla
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h
new file mode 100644
index 000000000..573fe6e76
--- /dev/null
+++ b/netwerk/base/Tickler.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Tickler_h
+#define mozilla_net_Tickler_h
+
+// The tickler sends a regular 0 byte UDP heartbeat out to a
+// particular address for a short time after it has been touched. This
+// is used on some mobile wifi chipsets to mitigate Power Save Polling
+// (PSP) Mode when we are anticipating a response packet
+// soon. Typically PSP adds 100ms of latency to a read event because
+// the packet delivery is not triggered until the 802.11 beacon is
+// delivered to the host (100ms is the standard Access Point
+// configuration for the beacon interval.) Requesting a frequent
+// transmission and getting a CTS frame from the AP at least that
+// frequently allows for low latency receives when we have reason to
+// expect them (e.g a SYN-ACK).
+//
+// The tickler is used to allow RTT based phases of web transport to
+// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
+// handshake, HTTP headers, and the TCP slow start phase. The
+// transaction is given up to 400 miliseconds by default to get
+// through those phases before the tickler is disabled.
+//
+// The tickler only applies to wifi on mobile right now. Hopefully it
+// can also be restricted to particular handset models in the future.
+
+#if defined(ANDROID) && !defined(MOZ_B2G)
+#define MOZ_USE_WIFI_TICKLER
+#endif
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include <stdint.h>
+
+#ifdef MOZ_USE_WIFI_TICKLER
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAutoPtr.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+
+class nsIPrefBranch;
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+// 8f769ed6-207c-4af9-9f7e-9e832da3754e
+#define NS_TICKLER_IID \
+{ 0x8f769ed6, 0x207c, 0x4af9, \
+ { 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e } }
+
+class Tickler final : public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID)
+
+ // These methods are main thread only
+ Tickler();
+ void Cancel();
+ nsresult Init();
+ void SetIPV4Address(uint32_t address);
+ void SetIPV4Port(uint16_t port);
+
+ // Tickle the tickler to (re-)start the activity.
+ // May call from any thread
+ void Tickle();
+
+private:
+ ~Tickler();
+
+ friend class TicklerTimer;
+ Mutex mLock;
+ nsCOMPtr<nsIThread> mThread;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIPrefBranch> mPrefs;
+
+ bool mActive;
+ bool mCanceled;
+ bool mEnabled;
+ uint32_t mDelay;
+ TimeDuration mDuration;
+ PRFileDesc* mFD;
+
+ TimeStamp mLastTickle;
+ PRNetAddr mAddr;
+
+ // These functions may be called from any thread
+ void PostCheckTickler();
+ void MaybeStartTickler();
+ void MaybeStartTicklerUnlocked();
+
+ // Tickler thread only
+ void CheckTickler();
+ void StartTickler();
+ void StopTickler();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID)
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+class Tickler final : public nsISupports
+{
+ ~Tickler() { }
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Tickler() { }
+ nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; }
+ void Cancel() { }
+ void SetIPV4Address(uint32_t) { };
+ void SetIPV4Port(uint16_t) { }
+ void Tickle() { }
+};
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Tickler_h
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
new file mode 100644
index 000000000..3b731db10
--- /dev/null
+++ b/netwerk/base/moz.build
@@ -0,0 +1,316 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'mozIThirdPartyUtil.idl',
+ 'nsIApplicationCache.idl',
+ 'nsIApplicationCacheChannel.idl',
+ 'nsIApplicationCacheContainer.idl',
+ 'nsIApplicationCacheService.idl',
+ 'nsIArrayBufferInputStream.idl',
+ 'nsIAsyncStreamCopier.idl',
+ 'nsIAsyncStreamCopier2.idl',
+ 'nsIAsyncVerifyRedirectCallback.idl',
+ 'nsIAuthInformation.idl',
+ 'nsIAuthModule.idl',
+ 'nsIAuthPrompt.idl',
+ 'nsIAuthPrompt2.idl',
+ 'nsIAuthPromptAdapterFactory.idl',
+ 'nsIAuthPromptCallback.idl',
+ 'nsIAuthPromptProvider.idl',
+ 'nsIBackgroundFileSaver.idl',
+ 'nsIBufferedStreams.idl',
+ 'nsIByteRangeRequest.idl',
+ 'nsICacheInfoChannel.idl',
+ 'nsICachingChannel.idl',
+ 'nsICancelable.idl',
+ 'nsICaptivePortalService.idl',
+ 'nsIChannel.idl',
+ 'nsIChannelEventSink.idl',
+ 'nsIChannelWithDivertableParentListener.idl',
+ 'nsIChildChannel.idl',
+ 'nsIClassOfService.idl',
+ 'nsIContentSniffer.idl',
+ 'nsICryptoFIPSInfo.idl',
+ 'nsICryptoHash.idl',
+ 'nsICryptoHMAC.idl',
+ 'nsIDashboard.idl',
+ 'nsIDashboardEventNotifier.idl',
+ 'nsIDeprecationWarner.idl',
+ 'nsIDivertableChannel.idl',
+ 'nsIDownloader.idl',
+ 'nsIEncodedChannel.idl',
+ 'nsIExternalProtocolHandler.idl',
+ 'nsIFileStreams.idl',
+ 'nsIFileURL.idl',
+ 'nsIForcePendingChannel.idl',
+ 'nsIFormPOSTActionChannel.idl',
+ 'nsIHttpAuthenticatorCallback.idl',
+ 'nsIHttpPushListener.idl',
+ 'nsIIncrementalDownload.idl',
+ 'nsIIncrementalStreamLoader.idl',
+ 'nsIInputStreamChannel.idl',
+ 'nsIInputStreamPump.idl',
+ 'nsIIOService.idl',
+ 'nsIIOService2.idl',
+ 'nsILoadContextInfo.idl',
+ 'nsILoadGroup.idl',
+ 'nsILoadGroupChild.idl',
+ 'nsILoadInfo.idl',
+ 'nsIMIMEInputStream.idl',
+ 'nsIMultiPartChannel.idl',
+ 'nsINestedURI.idl',
+ 'nsINetAddr.idl',
+ 'nsINetUtil.idl',
+ 'nsINetworkInfoService.idl',
+ 'nsINetworkInterceptController.idl',
+ 'nsINetworkLinkService.idl',
+ 'nsINetworkPredictor.idl',
+ 'nsINetworkPredictorVerifier.idl',
+ 'nsINetworkProperties.idl',
+ 'nsINSSErrorsService.idl',
+ 'nsINullChannel.idl',
+ 'nsIParentChannel.idl',
+ 'nsIParentRedirectingChannel.idl',
+ 'nsIPermission.idl',
+ 'nsIPermissionManager.idl',
+ 'nsIPrivateBrowsingChannel.idl',
+ 'nsIProgressEventSink.idl',
+ 'nsIPrompt.idl',
+ 'nsIProtocolHandler.idl',
+ 'nsIProtocolProxyCallback.idl',
+ 'nsIProtocolProxyFilter.idl',
+ 'nsIProtocolProxyService.idl',
+ 'nsIProtocolProxyService2.idl',
+ 'nsIProxiedChannel.idl',
+ 'nsIProxiedProtocolHandler.idl',
+ 'nsIProxyInfo.idl',
+ 'nsIRandomGenerator.idl',
+ 'nsIRedirectChannelRegistrar.idl',
+ 'nsIRedirectResultListener.idl',
+ 'nsIRequest.idl',
+ 'nsIRequestContext.idl',
+ 'nsIRequestObserver.idl',
+ 'nsIRequestObserverProxy.idl',
+ 'nsIResumableChannel.idl',
+ 'nsISecCheckWrapChannel.idl',
+ 'nsISecureBrowserUI.idl',
+ 'nsISecurityEventSink.idl',
+ 'nsISecurityInfoProvider.idl',
+ 'nsISensitiveInfoHiddenURI.idl',
+ 'nsISerializationHelper.idl',
+ 'nsIServerSocket.idl',
+ 'nsISimpleStreamListener.idl',
+ 'nsISocketFilter.idl',
+ 'nsISocketTransport.idl',
+ 'nsISocketTransportService.idl',
+ 'nsISpeculativeConnect.idl',
+ 'nsIStandardURL.idl',
+ 'nsIStreamingProtocolController.idl',
+ 'nsIStreamingProtocolService.idl',
+ 'nsIStreamListener.idl',
+ 'nsIStreamListenerTee.idl',
+ 'nsIStreamLoader.idl',
+ 'nsIStreamTransportService.idl',
+ 'nsISyncStreamListener.idl',
+ 'nsISystemProxySettings.idl',
+ 'nsIThreadRetargetableRequest.idl',
+ 'nsIThreadRetargetableStreamListener.idl',
+ 'nsIThrottledInputChannel.idl',
+ 'nsITimedChannel.idl',
+ 'nsITLSServerSocket.idl',
+ 'nsITraceableChannel.idl',
+ 'nsITransport.idl',
+ 'nsIUDPSocket.idl',
+ 'nsIUnicharStreamLoader.idl',
+ 'nsIUploadChannel.idl',
+ 'nsIUploadChannel2.idl',
+ 'nsIURI.idl',
+ 'nsIURIClassifier.idl',
+ 'nsIURIWithBlobImpl.idl',
+ 'nsIURIWithPrincipal.idl',
+ 'nsIURIWithQuery.idl',
+ 'nsIURL.idl',
+ 'nsIURLParser.idl',
+ 'nsPILoadGroupInternal.idl',
+ 'nsPISocketTransportService.idl',
+]
+
+if CONFIG['MOZ_TOOLKIT_SEARCH']:
+ XPIDL_SOURCES += [
+ 'nsIBrowserSearchService.idl',
+ ]
+
+XPIDL_MODULE = 'necko'
+
+EXPORTS += [
+ 'netCore.h',
+ 'nsASocketHandler.h',
+ 'nsAsyncRedirectVerifyHelper.h',
+ 'nsFileStreams.h',
+ 'nsInputStreamPump.h',
+ 'nsMIMEInputStream.h',
+ 'nsNetUtil.h',
+ 'nsNetUtilInlines.h',
+ 'nsReadLine.h',
+ 'nsSerializationHelper.h',
+ 'nsSimpleNestedURI.h',
+ 'nsSimpleURI.h',
+ 'nsStreamListenerWrapper.h',
+ 'nsTemporaryFileInputStream.h',
+ 'nsURIHashKey.h',
+ 'nsURLHelper.h',
+ 'nsURLParsers.h',
+]
+
+EXPORTS.mozilla += [
+ 'LoadContextInfo.h',
+ 'LoadInfo.h',
+ 'LoadTainting.h',
+]
+
+EXPORTS.mozilla.net += [
+ 'CaptivePortalService.h',
+ 'ChannelDiverterChild.h',
+ 'ChannelDiverterParent.h',
+ 'Dashboard.h',
+ 'DashboardTypes.h',
+ 'MemoryDownloader.h',
+ 'Predictor.h',
+ 'ReferrerPolicy.h',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ EXPORTS += [
+ 'NetStatistics.h',
+ ]
+
+UNIFIED_SOURCES += [
+ 'ArrayBufferInputStream.cpp',
+ 'BackgroundFileSaver.cpp',
+ 'CaptivePortalService.cpp',
+ 'ChannelDiverterChild.cpp',
+ 'ChannelDiverterParent.cpp',
+ 'Dashboard.cpp',
+ 'EventTokenBucket.cpp',
+ 'LoadContextInfo.cpp',
+ 'LoadInfo.cpp',
+ 'MemoryDownloader.cpp',
+ 'NetworkActivityMonitor.cpp',
+ 'nsAsyncRedirectVerifyHelper.cpp',
+ 'nsAsyncStreamCopier.cpp',
+ 'nsAuthInformationHolder.cpp',
+ 'nsBase64Encoder.cpp',
+ 'nsBaseChannel.cpp',
+ 'nsBaseContentStream.cpp',
+ 'nsBufferedStreams.cpp',
+ 'nsChannelClassifier.cpp',
+ 'nsDirectoryIndexStream.cpp',
+ 'nsDNSPrefetch.cpp',
+ 'nsDownloader.cpp',
+ 'nsFileStreams.cpp',
+ 'nsIncrementalDownload.cpp',
+ 'nsIncrementalStreamLoader.cpp',
+ 'nsInputStreamChannel.cpp',
+ 'nsInputStreamPump.cpp',
+ 'nsIOService.cpp',
+ 'nsLoadGroup.cpp',
+ 'nsMediaFragmentURIParser.cpp',
+ 'nsMIMEInputStream.cpp',
+ 'nsNetAddr.cpp',
+ 'nsNetUtil.cpp',
+ 'nsPACMan.cpp',
+ 'nsPreloadedStream.cpp',
+ 'nsProtocolProxyService.cpp',
+ 'nsProxyInfo.cpp',
+ 'nsRequestObserverProxy.cpp',
+ 'nsSecCheckWrapChannel.cpp',
+ 'nsSerializationHelper.cpp',
+ 'nsServerSocket.cpp',
+ 'nsSimpleNestedURI.cpp',
+ 'nsSimpleStreamListener.cpp',
+ 'nsSimpleURI.cpp',
+ 'nsSocketTransport2.cpp',
+ 'nsSocketTransportService2.cpp',
+ 'nsStandardURL.cpp',
+ 'nsStreamListenerTee.cpp',
+ 'nsStreamListenerWrapper.cpp',
+ 'nsStreamLoader.cpp',
+ 'nsStreamTransportService.cpp',
+ 'nsSyncStreamListener.cpp',
+ 'nsTemporaryFileInputStream.cpp',
+ 'nsTransportUtils.cpp',
+ 'nsUDPSocket.cpp',
+ 'nsUnicharStreamLoader.cpp',
+ 'nsURLHelper.cpp',
+ 'nsURLParsers.cpp',
+ 'PollableEvent.cpp',
+ 'Predictor.cpp',
+ 'ProxyAutoConfig.cpp',
+ 'RedirectChannelRegistrar.cpp',
+ 'RequestContextService.cpp',
+ 'SimpleBuffer.cpp',
+ 'StreamingProtocolService.cpp',
+ 'ThrottleQueue.cpp',
+ 'Tickler.cpp',
+ 'TLSServerSocket.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += [
+ 'nsURLHelperWin.cpp',
+ 'ShutdownLayer.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsURLHelperOSX.cpp',
+ ]
+else:
+ SOURCES += [
+ 'nsURLHelperUnix.cpp',
+ ]
+
+# nsINetworkInfoService support.
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += [
+ 'NetworkInfoServiceWindows.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'NetworkInfoServiceCocoa.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+elif CONFIG['OS_ARCH'] == 'Linux':
+ SOURCES += [
+ 'NetworkInfoServiceLinux.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
+
+EXTRA_JS_MODULES += [
+ 'NetUtil.jsm',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+ '/dom/base',
+ '/netwerk/protocol/http',
+ '/netwerk/socket',
+ '/security/pkix/include'
+]
+
+if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
+ LOCAL_INCLUDES += [
+ '/netwerk/protocol/rtsp/controller',
+ '/netwerk/protocol/rtsp/rtsp',
+ ]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl
new file mode 100644
index 000000000..2eea9550a
--- /dev/null
+++ b/netwerk/base/mozIThirdPartyUtil.idl
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsIChannel;
+
+/**
+ * Utility functions for determining whether a given URI, channel, or window
+ * hierarchy is third party with respect to a known URI.
+ */
+[scriptable, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)]
+interface mozIThirdPartyUtil : nsISupports
+{
+ /**
+ * isThirdPartyURI
+ *
+ * Determine whether two URIs are third party with respect to each other.
+ * This is determined by computing the base domain for both URIs. If they can
+ * be determined, and the base domains match, the request is defined as first
+ * party. If it cannot be determined because one or both URIs do not have a
+ * base domain (for instance, in the case of IP addresses, host aliases such
+ * as 'localhost', or a file:// URI), an exact string comparison on host is
+ * performed.
+ *
+ * For example, the URI "http://mail.google.com/" is not third party with
+ * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and
+ * "http://192.168.1.1/" are.
+ *
+ * @return true if aFirstURI is third party with respect to aSecondURI.
+ *
+ * @throws if either URI is null, has a malformed host, or has an empty host
+ * and is not a file:// URI.
+ */
+ boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI);
+
+ /**
+ * isThirdPartyWindow
+ *
+ * Determine whether the given window hierarchy is third party. This is done
+ * as follows:
+ *
+ * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the
+ * 'bottom URI'.
+ * 2) If 'aURI' is provided, determine if it is third party with respect to
+ * the bottom URI. If so, return.
+ * 3) Find the same-type parent window, if there is one, and its URI.
+ * Determine whether it is third party with respect to the bottom URI. If
+ * so, return.
+ *
+ * Therefore, each level in the window hierarchy is tested. (This means that
+ * nested iframes with different base domains, even though the bottommost and
+ * topmost URIs might be equal, will be considered third party.)
+ *
+ * @param aWindow
+ * The bottommost window in the hierarchy.
+ * @param aURI
+ * A URI to test against. If null, the URI of the principal
+ * associated with 'aWindow' will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if 'aURI' is third party with respect to any of the URIs
+ * associated with aWindow and its same-type parents.
+ *
+ * @throws if aWindow is null; the same-type parent of any window in the
+ * hierarchy cannot be determined; or the URI associated with any
+ * window in the hierarchy is null, has a malformed host, or has an
+ * empty host and is not a file:// URI.
+ *
+ * @see isThirdPartyURI
+ */
+ boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI);
+
+ /**
+ * isThirdPartyChannel
+ *
+ * Determine whether the given channel and its content window hierarchy is
+ * third party. This is done as follows:
+ *
+ * 1) If 'aChannel' is an nsIHttpChannel and has the
+ * 'forceAllowThirdPartyCookie' property set, then:
+ * a) If 'aURI' is null, return false.
+ * b) Otherwise, find the URI of the channel, determine whether it is
+ * foreign with respect to 'aURI', and return.
+ * 2) Find the URI of the channel and determine whether it is third party with
+ * respect to the URI of the channel. If so, return.
+ * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it
+ * exists, from the channel's notification callbacks. Then:
+ * a) If the parent is the same as the bottommost window, and the channel
+ * has the LOAD_DOCUMENT_URI flag set, return false. This represents the
+ * case where a toplevel load is occurring and the window's URI has not
+ * yet been updated. (We have already checked that 'aURI' is not foreign
+ * with respect to the channel URI.)
+ * b) Otherwise, return the result of isThirdPartyWindow with arguments
+ * of the channel's bottommost window and the channel URI, respectively.
+ *
+ * Therefore, both the channel's URI and each level in the window hierarchy
+ * associated with the channel is tested.
+ *
+ * @param aChannel
+ * The channel associated with the load.
+ * @param aURI
+ * A URI to test against. If null, the URI of the channel will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if aURI is third party with respect to the channel URI or any
+ * of the URIs associated with the same-type window hierarchy of the
+ * channel.
+ *
+ * @throws if 'aChannel' is null; the channel has no notification callbacks or
+ * an associated window; or isThirdPartyWindow throws.
+ *
+ * @see isThirdPartyWindow
+ */
+ boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI);
+
+ /**
+ * getBaseDomain
+ *
+ * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+ * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+ * dot may be present. If aHostURI is an IP address, an alias such as
+ * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
+ * be the exact host. The result of this function should only be used in exact
+ * string comparisons, since substring comparisons will not be valid for the
+ * special cases elided above.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomain(in nsIURI aHostURI);
+
+ /**
+ * getURIFromWindow
+ *
+ * Returns the URI associated with the script object principal for the
+ * window.
+ */
+ nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getTopWindowForChannel
+ *
+ * Returns the top-level window associated with the given channel.
+ */
+ mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel);
+};
+
+%{ C++
+/**
+ * The mozIThirdPartyUtil implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1"
+%}
+
diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h
new file mode 100644
index 000000000..7a0738cf9
--- /dev/null
+++ b/netwerk/base/netCore.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __netCore_h__
+#define __netCore_h__
+
+#include "nsError.h"
+
+// Where most necko status messages come from:
+#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties"
+
+#endif // __netCore_h__
diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h
new file mode 100644
index 000000000..c15daecd8
--- /dev/null
+++ b/netwerk/base/nsASocketHandler.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsASocketHandler_h__
+#define nsASocketHandler_h__
+
+// socket handler used by nsISocketTransportService.
+// methods are only called on the socket thread.
+
+class nsASocketHandler : public nsISupports
+{
+public:
+ nsASocketHandler()
+ : mCondition(NS_OK)
+ , mPollFlags(0)
+ , mPollTimeout(UINT16_MAX)
+ , mIsPrivate(false)
+ {}
+
+ //
+ // this condition variable will be checked to determine if the socket
+ // handler should be detached. it must only be accessed on the socket
+ // thread.
+ //
+ nsresult mCondition;
+
+ //
+ // these flags can only be modified on the socket transport thread.
+ // the socket transport service will check these flags before calling
+ // PR_Poll.
+ //
+ uint16_t mPollFlags;
+
+ //
+ // this value specifies the maximum amount of time in seconds that may be
+ // spent waiting for activity on this socket. if this timeout is reached,
+ // then OnSocketReady will be called with outFlags = -1.
+ //
+ // the default value for this member is UINT16_MAX, which disables the
+ // timeout error checking. (i.e., a timeout value of UINT16_MAX is
+ // never reached.)
+ //
+ uint16_t mPollTimeout;
+
+ bool mIsPrivate;
+
+ //
+ // called to service a socket
+ //
+ // params:
+ // socketRef - socket identifier
+ // fd - socket file descriptor
+ // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns
+ // or -1 if a timeout occurred
+ //
+ virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags) = 0;
+
+ //
+ // called when a socket is no longer under the control of the socket
+ // transport service. the socket handler may close the socket at this
+ // point. after this call returns, the handler will no longer be owned
+ // by the socket transport service.
+ //
+ virtual void OnSocketDetached(PRFileDesc *fd) = 0;
+
+ //
+ // called to determine if the socket is for a local peer.
+ // when used for server sockets, indicates if it only accepts local
+ // connections.
+ //
+ virtual void IsLocal(bool *aIsLocal) = 0;
+
+ //
+ // called to determine if this socket should not be terminated when Gecko
+ // is turned offline. This is mostly useful for the debugging server
+ // socket.
+ //
+ virtual void KeepWhenOffline(bool *aKeepWhenOffline)
+ {
+ *aKeepWhenOffline = false;
+ }
+
+ //
+ // called when global pref for keepalive has changed.
+ //
+ virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) { }
+
+ //
+ // returns the number of bytes sent/transmitted over the socket
+ //
+ virtual uint64_t ByteCountSent() = 0;
+ virtual uint64_t ByteCountReceived() = 0;
+};
+
+#endif // !nsASocketHandler_h__
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
new file mode 100644
index 000000000..3b19b93c7
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+
+#include "nsIOService.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRedirectLog("nsRedirect");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper,
+ nsIAsyncVerifyRedirectCallback,
+ nsIRunnable)
+
+class nsAsyncVerifyRedirectCallbackEvent : public Runnable {
+public:
+ nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb,
+ nsresult result)
+ : mCallback(cb), mResult(result) {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() "
+ "callback to %p with result %x",
+ mCallback.get(), mResult));
+ (void) mCallback->OnRedirectVerifyCallback(mResult);
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+ nsresult mResult;
+};
+
+nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper()
+ : mFlags(0),
+ mWaitingForRedirectCallback(false),
+ mCallbackInitiated(false),
+ mExpectedCallbacks(0),
+ mResult(NS_OK)
+{
+}
+
+nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper()
+{
+ NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
+ "Did not receive all required callbacks!");
+}
+
+nsresult
+nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags, bool synchronize)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::Init() "
+ "oldChan=%p newChan=%p", oldChan, newChan));
+ mOldChan = oldChan;
+ mNewChan = newChan;
+ mFlags = flags;
+ mCallbackThread = do_GetCurrentThread();
+
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsCOMPtr<nsILoadInfo> loadInfo = oldChan->GetLoadInfo();
+ if (loadInfo && loadInfo->GetDontFollowRedirects()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ }
+
+ if (synchronize)
+ mWaitingForRedirectCallback = true;
+
+ nsresult rv;
+ rv = NS_DispatchToMainThread(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (synchronize) {
+ nsIThread *thread = NS_GetCurrentThread();
+ while (mWaitingForRedirectCallback) {
+ if (!NS_ProcessNextEvent(thread)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
+ "result=%x expectedCBs=%u mResult=%x",
+ result, mExpectedCallbacks, mResult));
+
+ MOZ_DIAGNOSTIC_ASSERT(mExpectedCallbacks > 0,
+ "OnRedirectVerifyCallback called more times than expected");
+ if (mExpectedCallbacks <= 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ --mExpectedCallbacks;
+
+ // If response indicates failure we may call back immediately
+ if (NS_FAILED(result)) {
+ // We chose to store the first failure-value (as opposed to the last)
+ if (NS_SUCCEEDED(mResult))
+ mResult = result;
+
+ // If InitCallback() has been called, just invoke the callback and
+ // return. Otherwise it will be invoked from InitCallback()
+ if (mCallbackInitiated) {
+ ExplicitCallback(mResult);
+ return NS_OK;
+ }
+ }
+
+ // If the expected-counter is in balance and InitCallback() was called, all
+ // sinks have agreed that the redirect is ok and we can invoke our callback
+ if (mCallbackInitiated && mExpectedCallbacks == 0) {
+ ExplicitCallback(mResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink,
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
+ "sink=%p expectedCBs=%u mResult=%x",
+ sink, mExpectedCallbacks, mResult));
+
+ ++mExpectedCallbacks;
+
+ if (IsOldChannelCanceled()) {
+ LOG((" old channel has been canceled, cancel the redirect by "
+ "emulating OnRedirectVerifyCallback..."));
+ (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv =
+ sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+
+ LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks));
+
+ // If the sink returns failure from this call the redirect is vetoed. We
+ // emulate a callback from the sink in this case in order to perform all
+ // the necessary logic.
+ if (NS_FAILED(rv)) {
+ LOG((" emulating OnRedirectVerifyCallback..."));
+ (void) OnRedirectVerifyCallback(rv);
+ }
+
+ return rv; // Return the actual status since our caller may need it
+}
+
+void
+nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result)
+{
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x",
+ result, mExpectedCallbacks, mCallbackInitiated, mResult));
+
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback>
+ callback(do_QueryInterface(mOldChan));
+
+ if (!callback || !mCallbackThread) {
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get()));
+ return;
+ }
+
+ mCallbackInitiated = false; // reset to ensure only one callback
+ mWaitingForRedirectCallback = false;
+
+ // Now, dispatch the callback on the event-target which called Init()
+ nsCOMPtr<nsIRunnable> event =
+ new nsAsyncVerifyRedirectCallbackEvent(callback, result);
+ if (!event) {
+ NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed creating callback event!");
+ return;
+ }
+ nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed dispatching callback event!");
+ } else {
+ LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "dispatched callback event=%p", event.get()));
+ }
+
+}
+
+void
+nsAsyncRedirectVerifyHelper::InitCallback()
+{
+ LOG(("nsAsyncRedirectVerifyHelper::InitCallback() "
+ "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult));
+
+ mCallbackInitiated = true;
+
+ // Invoke the callback if we are done
+ if (mExpectedCallbacks == 0)
+ ExplicitCallback(mResult);
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::Run()
+{
+ /* If the channel got canceled after it fired AsyncOnChannelRedirect
+ * and before we got here, mostly because docloader load has been canceled,
+ * we must completely ignore this notification and prevent any further
+ * notification.
+ */
+ if (IsOldChannelCanceled()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ // First, the global observer
+ NS_ASSERTION(gIOService, "Must have an IO service at this point");
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
+ nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan,
+ mFlags, this);
+ if (NS_FAILED(rv)) {
+ ExplicitCallback(rv);
+ return NS_OK;
+ }
+
+ // Now, the per-channel observers
+ nsCOMPtr<nsIChannelEventSink> sink;
+ NS_QueryNotificationCallbacks(mOldChan, sink);
+ if (sink) {
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
+ rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
+ }
+
+ // All invocations to AsyncOnChannelRedirect has been done - call
+ // InitCallback() to flag this
+ InitCallback();
+ return NS_OK;
+}
+
+bool
+nsAsyncRedirectVerifyHelper::IsOldChannelCanceled()
+{
+ bool canceled;
+ nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal =
+ do_QueryInterface(mOldChan);
+ if (oldChannelInternal) {
+ oldChannelInternal->GetCanceled(&canceled);
+ if (canceled) {
+ return true;
+ }
+ } else if (mOldChan) {
+ // For non-HTTP channels check on the status, failure
+ // indicates the channel has probably been canceled.
+ nsresult status = NS_ERROR_FAILURE;
+ mOldChan->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h
new file mode 100644
index 000000000..f67785498
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncRedirectVerifyHelper_h
+#define nsAsyncRedirectVerifyHelper_h
+
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class simplifies call of OnChannelRedirect of IOService and
+ * the sink bound with the channel being redirected while the result of
+ * redirect decision is returned through the callback.
+ */
+class nsAsyncRedirectVerifyHelper final : public nsIRunnable,
+ public nsIAsyncVerifyRedirectCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+public:
+ nsAsyncRedirectVerifyHelper();
+
+ /*
+ * Calls AsyncOnChannelRedirect() on the given sink with the given
+ * channels and flags. Keeps track of number of async callbacks to expect.
+ */
+ nsresult DelegateOnChannelRedirect(nsIChannelEventSink *sink,
+ nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags);
+
+ /**
+ * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel
+ * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect
+ * decision is passed through this interface back to the oldChannel.
+ *
+ * @param oldChan
+ * channel being redirected, MUST implement
+ * nsIAsyncVerifyRedirectCallback
+ * @param newChan
+ * target of the redirect channel
+ * @param flags
+ * redirect flags
+ * @param synchronize
+ * set to TRUE if you want the Init method wait synchronously for
+ * all redirect callbacks
+ */
+ nsresult Init(nsIChannel* oldChan,
+ nsIChannel* newChan,
+ uint32_t flags,
+ bool synchronize = false);
+
+protected:
+ nsCOMPtr<nsIChannel> mOldChan;
+ nsCOMPtr<nsIChannel> mNewChan;
+ uint32_t mFlags;
+ bool mWaitingForRedirectCallback;
+ nsCOMPtr<nsIThread> mCallbackThread;
+ bool mCallbackInitiated;
+ int32_t mExpectedCallbacks;
+ nsresult mResult; // value passed to callback
+
+ void InitCallback();
+
+ /**
+ * Calls back to |oldChan| as described in Init()
+ */
+ void ExplicitCallback(nsresult result);
+
+private:
+ ~nsAsyncRedirectVerifyHelper();
+
+ bool IsOldChannelCanceled();
+};
+
+/*
+ * Helper to make the call-stack handle some control-flow for us
+ */
+class nsAsyncRedirectAutoCallback
+{
+public:
+ explicit nsAsyncRedirectAutoCallback(nsIAsyncVerifyRedirectCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ mResult = NS_OK;
+ }
+ ~nsAsyncRedirectAutoCallback()
+ {
+ if (mCallback)
+ mCallback->OnRedirectVerifyCallback(mResult);
+ }
+ /*
+ * Call this is you want it to call back with a different result-code
+ */
+ void SetResult(nsresult aRes)
+ {
+ mResult = aRes;
+ }
+ /*
+ * Call this is you want to avoid the callback
+ */
+ void DontCallback()
+ {
+ mCallback = nullptr;
+ }
+private:
+ nsIAsyncVerifyRedirectCallback* mCallback;
+ nsresult mResult;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp
new file mode 100644
index 000000000..6eec29d61
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.cpp
@@ -0,0 +1,419 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAsyncStreamCopier.h"
+#include "nsIOService.h"
+#include "nsIEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+#undef LOG
+//
+// MOZ_LOG=nsStreamCopier:5
+//
+static LazyLogModule gStreamCopierLog("nsStreamCopier");
+#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args)
+
+/**
+ * An event used to perform initialization off the main thread.
+ */
+class AsyncApplyBufferingPolicyEvent final: public Runnable
+{
+public:
+ /**
+ * @param aCopier
+ * The nsAsyncStreamCopier requesting the information.
+ */
+ explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier)
+ : mCopier(aCopier)
+ , mTarget(NS_GetCurrentThread())
+ { }
+ NS_IMETHOD Run() override
+ {
+ nsresult rv = mCopier->ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ return NS_OK;
+ }
+
+ rv = mTarget->Dispatch(NewRunnableMethod(mCopier,
+ &nsAsyncStreamCopier::AsyncCopyInternal),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<nsAsyncStreamCopier> mCopier;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+
+
+//-----------------------------------------------------------------------------
+
+nsAsyncStreamCopier::nsAsyncStreamCopier()
+ : mLock("nsAsyncStreamCopier.mLock")
+ , mMode(NS_ASYNCCOPY_VIA_READSEGMENTS)
+ , mChunkSize(nsIOService::gDefaultSegmentSize)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mShouldSniffBuffering(false)
+{
+ LOG(("Creating nsAsyncStreamCopier @%x\n", this));
+}
+
+nsAsyncStreamCopier::~nsAsyncStreamCopier()
+{
+ LOG(("Destroying nsAsyncStreamCopier @%x\n", this));
+}
+
+bool
+nsAsyncStreamCopier::IsComplete(nsresult *status)
+{
+ MutexAutoLock lock(mLock);
+ if (status)
+ *status = mStatus;
+ return !mIsPending;
+}
+
+nsIRequest*
+nsAsyncStreamCopier::AsRequest()
+{
+ return static_cast<nsIRequest*>(static_cast<nsIAsyncStreamCopier*>(this));
+}
+
+void
+nsAsyncStreamCopier::Complete(nsresult status)
+{
+ LOG(("nsAsyncStreamCopier::Complete [this=%p status=%x]\n", this, status));
+
+ nsCOMPtr<nsIRequestObserver> observer;
+ nsCOMPtr<nsISupports> ctx;
+ {
+ MutexAutoLock lock(mLock);
+ mCopierCtx = nullptr;
+
+ if (mIsPending) {
+ mIsPending = false;
+ mStatus = status;
+
+ // setup OnStopRequest callback and release references...
+ observer = mObserver;
+ mObserver = nullptr;
+ }
+ }
+
+ if (observer) {
+ LOG((" calling OnStopRequest [status=%x]\n", status));
+ observer->OnStopRequest(AsRequest(), ctx, status);
+ }
+}
+
+void
+nsAsyncStreamCopier::OnAsyncCopyComplete(void *closure, nsresult status)
+{
+ nsAsyncStreamCopier *self = (nsAsyncStreamCopier *) closure;
+ self->Complete(status);
+ NS_RELEASE(self); // addref'd in AsyncCopy
+}
+
+//-----------------------------------------------------------------------------
+// nsISupports
+
+// We cannot use simply NS_IMPL_ISUPPORTSx as both
+// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest
+
+NS_IMPL_ADDREF(nsAsyncStreamCopier)
+NS_IMPL_RELEASE(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_BEGIN
+NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2)
+NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, nsIAsyncStreamCopier)
+NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetName(nsACString &name)
+{
+ name.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::IsPending(bool *result)
+{
+ *result = !IsComplete();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetStatus(nsresult *status)
+{
+ IsComplete(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Cancel(nsresult status)
+{
+ nsCOMPtr<nsISupports> copierCtx;
+ {
+ MutexAutoLock lock(mLock);
+ if (!mIsPending)
+ return NS_OK;
+ copierCtx.swap(mCopierCtx);
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ NS_WARNING("cancel with non-failure status code");
+ status = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (copierCtx)
+ NS_CancelAsyncCopy(copierCtx, status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Suspend()
+{
+ NS_NOTREACHED("nsAsyncStreamCopier::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Resume()
+{
+ NS_NOTREACHED("nsAsyncStreamCopier::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ return NS_OK;
+}
+
+nsresult
+nsAsyncStreamCopier::InitInternal(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ NS_ASSERTION(!mSource && !mSink, "Init() called more than once");
+ if (chunkSize == 0) {
+ chunkSize = nsIOService::gDefaultSegmentSize;
+ }
+ mChunkSize = chunkSize;
+
+ mSource = source;
+ mSink = sink;
+ mCloseSource = closeSource;
+ mCloseSink = closeSink;
+
+ if (target) {
+ mTarget = target;
+ } else {
+ nsresult rv;
+ mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered,
+ bool sinkBuffered,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ NS_ASSERTION(sourceBuffered || sinkBuffered, "at least one stream must be buffered");
+ mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS
+ : NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink)
+{
+ mShouldSniffBuffering = true;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+/**
+ * Detect whether the input or the output stream is buffered,
+ * bufferize one of them if neither is buffered.
+ */
+nsresult
+nsAsyncStreamCopier::ApplyBufferingPolicy()
+{
+ // This function causes I/O, it must not be executed on the main
+ // thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (NS_OutputStreamIsBuffered(mSink)) {
+ // Sink is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ return NS_OK;
+ }
+ if (NS_InputStreamIsBuffered(mSource)) {
+ // Source is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_READSEGMENTS;
+ return NS_OK;
+ }
+
+ // No buffering, let's buffer the sink
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> sink =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sink->Init(mSink, mChunkSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ mSink = sink;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx)
+{
+ LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%x]\n", this, observer));
+
+ NS_ASSERTION(mSource && mSink, "not initialized");
+ nsresult rv;
+
+ if (observer) {
+ // build proxy for observer events
+ rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // from this point forward, AsyncCopy is going to return NS_OK. any errors
+ // will be reported via OnStopRequest.
+ mIsPending = true;
+
+ if (mObserver) {
+ rv = mObserver->OnStartRequest(AsRequest(), nullptr);
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ }
+
+ if (!mShouldSniffBuffering) {
+ // No buffer sniffing required, let's proceed
+ AsyncCopyInternal();
+ return NS_OK;
+ }
+
+ if (NS_IsMainThread()) {
+ // Don't perform buffer sniffing on the main thread
+ nsCOMPtr<nsIRunnable> event = new AsyncApplyBufferingPolicyEvent(this);
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ // We're not going to block the main thread, so let's sniff here
+ rv = ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ AsyncCopyInternal();
+ return NS_OK;
+}
+
+// Launch async copy.
+// All errors are reported through the observer.
+void
+nsAsyncStreamCopier::AsyncCopyInternal()
+{
+ MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS
+ || mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+
+ nsresult rv;
+ // we want to receive progress notifications; release happens in
+ // OnAsyncCopyComplete.
+ NS_ADDREF_THIS();
+ {
+ MutexAutoLock lock(mLock);
+ rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize,
+ OnAsyncCopyComplete, this, mCloseSource, mCloseSink,
+ getter_AddRefs(mCopierCtx));
+ }
+ if (NS_FAILED(rv)) {
+ NS_RELEASE_THIS();
+ Cancel(rv);
+ }
+}
+
+
diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h
new file mode 100644
index 000000000..7529a327a
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.h
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncStreamCopier_h__
+#define nsAsyncStreamCopier_h__
+
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "mozilla/Mutex.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIRequestObserver;
+
+//-----------------------------------------------------------------------------
+
+class nsAsyncStreamCopier final : public nsIAsyncStreamCopier,
+ nsIAsyncStreamCopier2
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIASYNCSTREAMCOPIER
+
+ // nsIAsyncStreamCopier2
+ // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2
+ // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier
+ NS_IMETHOD Init(nsIInputStream *aSource,
+ nsIOutputStream *aSink,
+ nsIEventTarget *aTarget,
+ uint32_t aChunkSize,
+ bool aCloseSource,
+ bool aCloseSink) override;
+
+ nsAsyncStreamCopier();
+
+ //-------------------------------------------------------------------------
+ // these methods may be called on any thread
+
+ bool IsComplete(nsresult *status = nullptr);
+ void Complete(nsresult status);
+
+private:
+ virtual ~nsAsyncStreamCopier();
+
+ nsresult InitInternal(nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ uint32_t chunkSize,
+ bool closeSource,
+ bool closeSink);
+
+ static void OnAsyncCopyComplete(void *, nsresult);
+
+ void AsyncCopyInternal();
+ nsresult ApplyBufferingPolicy();
+ nsIRequest* AsRequest();
+
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ nsCOMPtr<nsISupports> mCopierCtx;
+
+ mozilla::Mutex mLock;
+
+ nsAsyncCopyMode mMode;
+ uint32_t mChunkSize;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mCloseSource;
+ bool mCloseSink;
+ bool mShouldSniffBuffering;
+
+ friend class ProceedWithAsyncCopy;
+ friend class AsyncApplyBufferingPolicyEvent;
+};
+
+#endif // !nsAsyncStreamCopier_h__
diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp
new file mode 100644
index 000000000..f52ff5454
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.cpp
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAuthInformationHolder.h"
+
+NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation)
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetFlags(uint32_t* aFlags)
+{
+ *aFlags = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetRealm(nsAString& aRealm)
+{
+ aRealm = mRealm;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme)
+{
+ aScheme = mAuthType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetUsername(nsAString& aUserName)
+{
+ aUserName = mUser;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetUsername(const nsAString& aUserName)
+{
+ if (!(mFlags & ONLY_PASSWORD))
+ mUser = aUserName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetPassword(nsAString& aPassword)
+{
+ aPassword = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetPassword(const nsAString& aPassword)
+{
+ mPassword = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetDomain(nsAString& aDomain)
+{
+ aDomain = mDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetDomain(const nsAString& aDomain)
+{
+ if (mFlags & NEED_DOMAIN)
+ mDomain = aDomain;
+ return NS_OK;
+}
+
+
diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h
new file mode 100644
index 000000000..b24bc743f
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef NSAUTHINFORMATIONHOLDER_H_
+#define NSAUTHINFORMATIONHOLDER_H_
+
+#include "nsIAuthInformation.h"
+#include "nsString.h"
+
+class nsAuthInformationHolder : public nsIAuthInformation {
+
+protected:
+ virtual ~nsAuthInformationHolder() {}
+
+public:
+ // aAuthType must be ASCII
+ nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm,
+ const nsCString& aAuthType)
+ : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHINFORMATION
+
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPassword; }
+ const nsString& Domain() const { return mDomain; }
+
+ /**
+ * This method can be used to initialize the username when the
+ * ONLY_PASSWORD flag is set.
+ */
+ void SetUserInternal(const nsString& aUsername) {
+ mUser = aUsername;
+ }
+private:
+ nsString mUser;
+ nsString mPassword;
+ nsString mDomain;
+
+ uint32_t mFlags;
+ nsString mRealm;
+ nsCString mAuthType;
+};
+
+
+#endif
diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp
new file mode 100644
index 000000000..f112be750
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.cpp
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBase64Encoder.h"
+
+#include "plbase64.h"
+#include "prmem.h"
+
+NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream)
+
+NS_IMETHODIMP
+nsBase64Encoder::Close()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ mData.Append(aBuf, aCount);
+ *_retval = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount,
+ uint32_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult
+nsBase64Encoder::Finish(nsCSubstring& result)
+{
+ char* b64 = PL_Base64Encode(mData.get(), mData.Length(), nullptr);
+ if (!b64)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ result.Assign(b64);
+ PR_Free(b64);
+ // Free unneeded memory and allow reusing the object
+ mData.Truncate();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h
new file mode 100644
index 000000000..ff61de51e
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSBASE64ENCODER_H_
+#define NSBASE64ENCODER_H_
+
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A base64 encoder. Usage: Instantiate class, write to it using
+ * Write(), then call Finish() to get the base64-encoded data.
+ */
+class nsBase64Encoder final : public nsIOutputStream {
+ public:
+ nsBase64Encoder() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult Finish(nsCSubstring& _result);
+ private:
+ ~nsBase64Encoder() {}
+
+ /// The data written to this stream. nsCString can deal fine with
+ /// binary data.
+ nsCString mData;
+};
+
+#endif
diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp
new file mode 100644
index 000000000..200804c1e
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -0,0 +1,937 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseChannel.h"
+#include "nsContentUtils.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsMimeTypes.h"
+#include "nsIContentSniffer.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpEventSink.h"
+#include "nsIHttpChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIStreamConverterService.h"
+#include "nsChannelClassifier.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsProxyRelease.h"
+#include "nsXULAppAPI.h"
+#include "nsContentSecurityManager.h"
+#include "LoadInfo.h"
+#include "nsServiceManagerUtils.h"
+
+// This class is used to suspend a request across a function scope.
+class ScopedRequestSuspender {
+public:
+ explicit ScopedRequestSuspender(nsIRequest *request)
+ : mRequest(request) {
+ if (mRequest && NS_FAILED(mRequest->Suspend())) {
+ NS_WARNING("Couldn't suspend pump");
+ mRequest = nullptr;
+ }
+ }
+ ~ScopedRequestSuspender() {
+ if (mRequest)
+ mRequest->Resume();
+ }
+private:
+ nsIRequest *mRequest;
+};
+
+// Used to suspend data events from mPump within a function scope. This is
+// usually needed when a function makes callbacks that could process events.
+#define SUSPEND_PUMP_FOR_SCOPE() \
+ ScopedRequestSuspender pump_suspender__(mPump)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel
+
+nsBaseChannel::nsBaseChannel()
+ : mLoadFlags(LOAD_NORMAL)
+ , mQueriedProgressSink(true)
+ , mSynthProgressEvents(false)
+ , mAllowThreadRetargeting(true)
+ , mWaitingOnAsyncRedirect(false)
+ , mStatus(NS_OK)
+ , mContentDispositionHint(UINT32_MAX)
+ , mContentLength(-1)
+ , mWasOpened(false)
+{
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsBaseChannel::~nsBaseChannel()
+{
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+}
+
+nsresult
+nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags,
+ bool openNewChannel)
+{
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Transfer properties
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ if (mLoadInfo) {
+ nsSecurityFlags secFlags = mLoadInfo->GetSecurityFlags() &
+ ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->CloneWithNewSecFlags(secFlags);
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager *sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal));
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+ newLoadInfo->AppendRedirectedPrincipal(uriPrincipal, isInternalRedirect);
+ newChannel->SetLoadInfo(newLoadInfo);
+ }
+ else {
+ // the newChannel was created with a dummy loadInfo, we should clear
+ // it in case the original channel does not have a loadInfo
+ newChannel->SetLoadInfo(nullptr);
+ }
+
+ // Preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel);
+ if (bag) {
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ bag->SetProperty(iter.Key(), iter.UserData());
+ }
+ }
+
+ // Notify consumer, giving chance to cancel redirect. For backwards compat,
+ // we support nsIHttpEventSink if we are an HTTP channel and if this is not
+ // an internal redirect.
+
+ RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
+ new nsAsyncRedirectVerifyHelper();
+
+ bool checkRedirectSynchronously = !openNewChannel;
+
+ mRedirectChannel = newChannel;
+ mRedirectFlags = redirectFlags;
+ mOpenRedirectChannel = openNewChannel;
+ nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags,
+ checkRedirectSynchronously);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (checkRedirectSynchronously && NS_FAILED(mStatus))
+ return mStatus;
+
+ return NS_OK;
+}
+
+nsresult
+nsBaseChannel::ContinueRedirect()
+{
+ // Backwards compat for non-internal redirects from a HTTP channel.
+ // XXX Is our http channel implementation going to derive from nsBaseChannel?
+ // If not, this code can be removed.
+ if (!(mRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface();
+ if (httpChannel) {
+ nsCOMPtr<nsIHttpEventSink> httpEventSink;
+ GetCallback(httpEventSink);
+ if (httpEventSink) {
+ nsresult rv = httpEventSink->OnRedirect(httpChannel, mRedirectChannel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // Make sure to do this _after_ making all the OnChannelRedirect calls
+ mRedirectChannel->SetOriginalURI(OriginalURI());
+
+ // If we fail to open the new channel, then we want to leave this channel
+ // unaffected, so we defer tearing down our channel until we have succeeded
+ // with the redirect.
+
+ if (mOpenRedirectChannel) {
+ nsresult rv = NS_OK;
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
+ rv = mRedirectChannel->AsyncOpen2(mListener);
+ }
+ else {
+ rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = nullptr;
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+ ChannelDone();
+
+ return NS_OK;
+}
+
+bool
+nsBaseChannel::HasContentTypeHint() const
+{
+ NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
+ return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsresult
+nsBaseChannel::PushStreamConverter(const char *fromType,
+ const char *toType,
+ bool invalidatesContentLength,
+ nsIStreamListener **result)
+{
+ NS_ASSERTION(mListener, "no listener");
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> scs =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = scs->AsyncConvertData(fromType, toType, mListener, mListenerContext,
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ if (invalidatesContentLength)
+ mContentLength = -1;
+ if (result) {
+ *result = nullptr;
+ converter.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsBaseChannel::BeginPumpingData()
+{
+ nsCOMPtr<nsIInputStream> stream;
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = OpenContentStream(true, getter_AddRefs(stream),
+ getter_AddRefs(channel));
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
+
+ if (channel) {
+ rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel));
+ if (NS_SUCCEEDED(rv))
+ mWaitingOnAsyncRedirect = true;
+ return rv;
+ }
+
+ // By assigning mPump, we flag this channel as pending (see Pending). It's
+ // important that the pending flag is set when we call into the stream (the
+ // call to AsyncRead results in the stream's AsyncWait method being called)
+ // and especially when we call into the loadgroup. Our caller takes care to
+ // release mPump if we return an error.
+
+ rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0,
+ true);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+
+ return rv;
+}
+
+void
+nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel)
+{
+ NS_ASSERTION(!mPump, "Shouldn't have gotten here");
+
+ nsresult rv = mStatus;
+ if (NS_SUCCEEDED(mStatus)) {
+ rv = Redirect(newChannel,
+ nsIChannelEventSink::REDIRECT_TEMPORARY,
+ true);
+ if (NS_SUCCEEDED(rv)) {
+ // OnRedirectVerifyCallback will be called asynchronously
+ return;
+ }
+ }
+
+ ContinueHandleAsyncRedirect(rv);
+}
+
+void
+nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result)
+{
+ mWaitingOnAsyncRedirect = false;
+
+ if (NS_FAILED(result))
+ Cancel(result);
+
+ if (NS_FAILED(result) && mListener) {
+ // Notify our consumer ourselves
+ mListener->OnStartRequest(this, mListenerContext);
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ ChannelDone();
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+}
+
+void
+nsBaseChannel::ClassifyURI()
+{
+ // For channels created in the child process, delegate to the parent to
+ // classify URIs.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (mLoadFlags & LOAD_CLASSIFY_URI) {
+ RefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
+ if (classifier) {
+ classifier->Start(this);
+ } else {
+ Cancel(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsBaseChannel,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIThreadRetargetableRequest,
+ nsIInterfaceRequestor,
+ nsITransportEventSink,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIThreadRetargetableStreamListener,
+ nsIAsyncVerifyRedirectCallback,
+ nsIPrivateBrowsingChannel)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequest
+
+NS_IMETHODIMP
+nsBaseChannel::GetName(nsACString &result)
+{
+ if (!mURI) {
+ result.Truncate();
+ return NS_OK;
+ }
+ return mURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::IsPending(bool *result)
+{
+ *result = Pending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetStatus(nsresult *status)
+{
+ if (mPump && NS_SUCCEEDED(mStatus)) {
+ mPump->GetStatus(status);
+ } else {
+ *status = mStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Cancel(nsresult status)
+{
+ // Ignore redundant cancelation
+ if (NS_FAILED(mStatus))
+ return NS_OK;
+
+ mStatus = status;
+
+ if (mPump)
+ mPump->Cancel(status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Suspend()
+{
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+ return mPump->Suspend();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Resume()
+{
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+ return mPump->Resume();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIChannel
+
+NS_IMETHODIMP
+nsBaseChannel::GetOriginalURI(nsIURI **aURI)
+{
+ *aURI = OriginalURI();
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOriginalURI(nsIURI *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetOwner(nsISupports **aOwner)
+{
+ NS_IF_ADDREF(*aOwner = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOwner(nsISupports *aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentType(nsACString &aContentType)
+{
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentType(const nsACString &aContentType)
+{
+ // mContentCharset is unchanged if not parsed
+ bool dummy;
+ net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ // preserve old behavior, fail unless explicitly set.
+ if (mContentDispositionHint == UINT32_MAX) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ if (!mContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ mContentDispositionFilename = new nsString(aContentDispositionFilename);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentLength(int64_t *aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open(nsIInputStream **result)
+{
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ nsCOMPtr<nsIChannel> chan;
+ nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan));
+ NS_ASSERTION(!chan || !*result, "Got both a channel and a stream?");
+ if (NS_SUCCEEDED(rv) && chan) {
+ rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = chan->Open(result);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED)
+ return NS_ImplementChannelOpen(this, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mWasOpened = true;
+ ClassifyURI();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+ NS_ENSURE_ARG(listener);
+
+ // Skip checking for chrome:// sub-resources.
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("file")) {
+ NS_CompareLoadInfoAndLoadContext(this);
+ }
+
+ // Ensure that this is an allowed port before proceeding.
+ nsresult rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // Store the listener and context early so that OpenContentStream and the
+ // stream's AsyncWait method (called by AsyncRead) can have access to them
+ // via PushStreamConverter and the StreamListener methods. However, since
+ // this typically introduces a reference cycle between this and the listener,
+ // we need to be sure to break the reference if this method does not succeed.
+ mListener = listener;
+ mListenerContext = ctxt;
+
+ // This method assigns mPump as a side-effect. We need to clear mPump if
+ // this method fails.
+ rv = BeginPumpingData();
+ if (NS_FAILED(rv)) {
+ mPump = nullptr;
+ ChannelDone();
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // At this point, we are going to return success no matter what.
+
+ mWasOpened = true;
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ ClassifyURI();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsITransportEventSink
+
+NS_IMETHODIMP
+nsBaseChannel::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ // In some cases, we may wish to suppress transport-layer status events.
+
+ if (!mPump || NS_FAILED(mStatus)) {
+ return NS_OK;
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Lazily fetch mProgressSink
+ if (!mProgressSink) {
+ if (mQueriedProgressSink) {
+ return NS_OK;
+ }
+ GetCallback(mProgressSink);
+ mQueriedProgressSink = true;
+ if (!mProgressSink) {
+ return NS_OK;
+ }
+ }
+
+ if (!HasLoadFlag(LOAD_BACKGROUND)) {
+ nsAutoString statusArg;
+ if (GetStatusArg(status, statusArg)) {
+ mProgressSink->OnStatus(this, mListenerContext, status, statusArg.get());
+ }
+ }
+
+ if (progress) {
+ mProgressSink->OnProgress(this, mListenerContext, progress, progressMax);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsBaseChannel::GetInterface(const nsIID &iid, void **result)
+{
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result);
+ return *result ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequestObserver
+
+static void
+CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
+{
+ nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+static void
+CallUnknownTypeSniffer(void *aClosure, const uint8_t *aData, uint32_t aCount)
+{
+ nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
+
+ nsCOMPtr<nsIContentSniffer> sniffer =
+ do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER);
+ if (!sniffer)
+ return;
+
+ nsAutoCString detected;
+ nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
+ if (NS_SUCCEEDED(rv))
+ chan->SetContentType(detected);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ MOZ_ASSERT(request == mPump);
+
+ // If our content type is unknown, use the content type
+ // sniffer. If the sniffer is not available for some reason, then we just keep
+ // going as-is.
+ if (NS_SUCCEEDED(mStatus) &&
+ mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+ }
+
+ // Now, the general type sniffers. Skip this if we have none.
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS)
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mListener) // null in case of redirect
+ return mListener->OnStartRequest(this, mListenerContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
+ nsresult status)
+{
+ // If both mStatus and status are failure codes, we keep mStatus as-is since
+ // that is consistent with our GetStatus and Cancel methods.
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ // Cause Pending to return false.
+ mPump = nullptr;
+
+ if (mListener) // null in case of redirect
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ ChannelDone();
+
+ // No need to suspend pump in this scope since we will not be receiving
+ // any more events from it.
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIStreamListener
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *stream, uint64_t offset,
+ uint32_t count)
+{
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ nsresult rv = mListener->OnDataAvailable(this, mListenerContext, stream,
+ offset, count);
+ if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
+ int64_t prog = offset + count;
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength);
+ } else {
+ class OnTransportStatusAsyncEvent : public mozilla::Runnable
+ {
+ RefPtr<nsBaseChannel> mChannel;
+ int64_t mProgress;
+ int64_t mContentLength;
+ public:
+ OnTransportStatusAsyncEvent(nsBaseChannel* aChannel,
+ int64_t aProgress,
+ int64_t aContentLength)
+ : mChannel(aChannel),
+ mProgress(aProgress),
+ mContentLength(aContentLength)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING,
+ mProgress, mContentLength);
+ }
+ };
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new OnTransportStatusAsyncEvent(this, prog, mContentLength);
+ NS_DispatchToMainThread(runnable);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnRedirectVerifyCallback(nsresult result)
+{
+ if (NS_SUCCEEDED(result))
+ result = ContinueRedirect();
+
+ if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = result;
+ return NS_OK;
+ }
+
+ if (mWaitingOnAsyncRedirect)
+ ContinueHandleAsyncRedirect(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return mPump->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h
new file mode 100644
index 000000000..b98609e85
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.h
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseChannel_h__
+#define nsBaseChannel_h__
+
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsInputStreamPump.h"
+
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIProgressEventSink.h"
+#include "nsITransport.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "PrivateBrowsingChannel.h"
+#include "nsThreadUtils.h"
+
+class nsIInputStream;
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel is designed to be subclassed. The subclass is responsible for
+// implementing the OpenContentStream method, which will be called by the
+// nsIChannel::AsyncOpen and nsIChannel::Open implementations.
+//
+// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way
+// for subclasses to query both the nsIChannel::notificationCallbacks and
+// nsILoadGroup::notificationCallbacks for supported interfaces.
+//
+// nsBaseChannel implements nsITransportEventSink to support progress & status
+// notifications generated by the transport layer.
+
+class nsBaseChannel : public nsHashPropertyBag
+ , public nsIChannel
+ , public nsIThreadRetargetableRequest
+ , public nsIInterfaceRequestor
+ , public nsITransportEventSink
+ , public nsIAsyncVerifyRedirectCallback
+ , public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>
+ , protected nsIStreamListener
+ , protected nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsBaseChannel();
+
+ // This method must be called to initialize the basechannel instance.
+ nsresult Init() {
+ return NS_OK;
+ }
+
+protected:
+ // -----------------------------------------------
+ // Methods to be implemented by the derived class:
+
+ virtual ~nsBaseChannel();
+
+private:
+ // Implemented by subclass to supply data stream. The parameter, async, is
+ // true when called from nsIChannel::AsyncOpen and false otherwise. When
+ // async is true, the resulting stream will be used with a nsIInputStreamPump
+ // instance. This means that if it is a non-blocking stream that supports
+ // nsIAsyncInputStream that it will be read entirely on the main application
+ // thread, and its AsyncWait method will be called whenever ReadSegments
+ // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking,
+ // then it will be read on one of the background I/O threads, and it does not
+ // need to implement ReadSegments. If async is false, this method may return
+ // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in
+ // terms of AsyncOpen (see NS_ImplementChannelOpen).
+ // A callee is allowed to return an nsIChannel instead of an nsIInputStream.
+ // That case will be treated as a redirect to the new channel. By default
+ // *channel will be set to null by the caller, so callees who don't want to
+ // return one an just not touch it.
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **stream,
+ nsIChannel** channel) = 0;
+
+ // The basechannel calls this method from its OnTransportStatus method to
+ // determine whether to call nsIProgressEventSink::OnStatus in addition to
+ // nsIProgressEventSink::OnProgress. This method may be overriden by the
+ // subclass to enable nsIProgressEventSink::OnStatus events. If this method
+ // returns true, then the statusArg out param specifies the "statusArg" value
+ // to pass to the OnStatus method. By default, OnStatus messages are
+ // suppressed. The status parameter passed to this method is the status value
+ // from the OnTransportStatus method.
+ virtual bool GetStatusArg(nsresult status, nsString &statusArg) {
+ return false;
+ }
+
+ // Called when the callbacks available to this channel may have changed.
+ virtual void OnCallbacksChanged() {
+ }
+
+ // Called when our channel is done, to allow subclasses to drop resources.
+ virtual void OnChannelDone() {
+ }
+
+public:
+ // ----------------------------------------------
+ // Methods provided for use by the derived class:
+
+ // Redirect to another channel. This method takes care of notifying
+ // observers of this redirect as well as of opening the new channel, if asked
+ // to do so. It also cancels |this| with the status code
+ // NS_BINDING_REDIRECTED. A failure return from this method means that the
+ // redirect could not be performed (no channel was opened; this channel
+ // wasn't canceled.) The redirectFlags parameter consists of the flag values
+ // defined on nsIChannelEventSink.
+ nsresult Redirect(nsIChannel *newChannel, uint32_t redirectFlags,
+ bool openNewChannel);
+
+ // Tests whether a type hint was set. Subclasses can use this to decide
+ // whether to call SetContentType.
+ // NOTE: This is only reliable if the subclass didn't itself call
+ // SetContentType, and should also not be called after OpenContentStream.
+ bool HasContentTypeHint() const;
+
+ // The URI member should be initialized before the channel is used, and then
+ // it should never be changed again until the channel is destroyed.
+ nsIURI *URI() {
+ return mURI;
+ }
+ void SetURI(nsIURI *uri) {
+ NS_ASSERTION(uri, "must specify a non-null URI");
+ NS_ASSERTION(!mURI, "must not modify URI");
+ NS_ASSERTION(!mOriginalURI, "how did that get set so early?");
+ mURI = uri;
+ mOriginalURI = uri;
+ }
+ nsIURI *OriginalURI() {
+ return mOriginalURI;
+ }
+
+ // The security info is a property of the transport-layer, which should be
+ // assigned by the subclass.
+ nsISupports *SecurityInfo() {
+ return mSecurityInfo;
+ }
+ void SetSecurityInfo(nsISupports *info) {
+ mSecurityInfo = info;
+ }
+
+ // Test the load flags
+ bool HasLoadFlag(uint32_t flag) {
+ return (mLoadFlags & flag) != 0;
+ }
+
+ // This is a short-cut to calling nsIRequest::IsPending()
+ virtual bool Pending() const {
+ return mPump || mWaitingOnAsyncRedirect;
+ }
+
+ // Helper function for querying the channel's notification callbacks.
+ template <class T> void GetCallback(nsCOMPtr<T> &result) {
+ GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
+ }
+
+ // Helper function for calling QueryInterface on this.
+ nsQueryInterface do_QueryInterface() {
+ return nsQueryInterface(static_cast<nsIChannel *>(this));
+ }
+ // MSVC needs this:
+ nsQueryInterface do_QueryInterface(nsISupports *obj) {
+ return nsQueryInterface(obj);
+ }
+
+ // If a subclass does not want to feed transport-layer progress events to the
+ // base channel via nsITransportEventSink, then it may set this flag to cause
+ // the base channel to synthesize progress events when it receives data from
+ // the content stream. By default, progress events are not synthesized.
+ void EnableSynthesizedProgressEvents(bool enable) {
+ mSynthProgressEvents = enable;
+ }
+
+ // Some subclasses may wish to manually insert a stream listener between this
+ // and the channel's listener. The following methods make that possible.
+ void SetStreamListener(nsIStreamListener *listener) {
+ mListener = listener;
+ }
+ nsIStreamListener *StreamListener() {
+ return mListener;
+ }
+
+ // Pushes a new stream converter in front of the channel's stream listener.
+ // The fromType and toType values are passed to nsIStreamConverterService's
+ // AsyncConvertData method. If invalidatesContentLength is true, then the
+ // channel's content-length property will be assigned a value of -1. This is
+ // necessary when the converter changes the length of the resulting data
+ // stream, which is almost always the case for a "stream converter" ;-)
+ // This function optionally returns a reference to the new converter.
+ nsresult PushStreamConverter(const char *fromType, const char *toType,
+ bool invalidatesContentLength = true,
+ nsIStreamListener **converter = nullptr);
+
+protected:
+ void DisallowThreadRetargeting() {
+ mAllowThreadRetargeting = false;
+ }
+
+private:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // Called to setup mPump and call AsyncRead on it.
+ nsresult BeginPumpingData();
+
+ // Called when the callbacks available to this channel may have changed.
+ void CallbacksChanged() {
+ mProgressSink = nullptr;
+ mQueriedProgressSink = false;
+ OnCallbacksChanged();
+ }
+
+ // Called when our channel is done. This should drop no-longer-needed pointers.
+ void ChannelDone() {
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ OnChannelDone();
+ }
+
+ // Handle an async redirect callback. This will only be called if we
+ // returned success from AsyncOpen while posting a redirect runnable.
+ void HandleAsyncRedirect(nsIChannel* newChannel);
+ void ContinueHandleAsyncRedirect(nsresult result);
+ nsresult ContinueRedirect();
+
+ // start URI classifier if requested
+ void ClassifyURI();
+
+ class RedirectRunnable : public mozilla::Runnable
+ {
+ public:
+ RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel)
+ : mChannel(chan), mNewChannel(newChannel)
+ {
+ NS_PRECONDITION(newChannel, "Must have channel to redirect to");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->HandleAsyncRedirect(mNewChannel);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseChannel> mChannel;
+ nsCOMPtr<nsIChannel> mNewChannel;
+ };
+ friend class RedirectRunnable;
+
+ RefPtr<nsInputStreamPump> mPump;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ uint32_t mLoadFlags;
+ bool mQueriedProgressSink;
+ bool mSynthProgressEvents;
+ bool mAllowThreadRetargeting;
+ bool mWaitingOnAsyncRedirect;
+ bool mOpenRedirectChannel;
+ uint32_t mRedirectFlags;
+
+protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsresult mStatus;
+ uint32_t mContentDispositionHint;
+ nsAutoPtr<nsString> mContentDispositionFilename;
+ int64_t mContentLength;
+ bool mWasOpened;
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsBaseChannel>;
+};
+
+#endif // !nsBaseChannel_h__
diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp
new file mode 100644
index 000000000..ee5a8ef3c
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseContentStream.h"
+#include "nsStreamUtils.h"
+
+//-----------------------------------------------------------------------------
+
+void
+nsBaseContentStream::DispatchCallback(bool async)
+{
+ if (!mCallback)
+ return;
+
+ // It's important to clear mCallback and mCallbackTarget up-front because the
+ // OnInputStreamReady implementation may call our AsyncWait method.
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (async) {
+ callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
+ mCallback = nullptr;
+ } else {
+ callback.swap(mCallback);
+ }
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsISupports
+
+NS_IMPL_ADDREF(nsBaseContentStream)
+NS_IMPL_RELEASE(nsBaseContentStream)
+
+// We only support nsIAsyncInputStream when we are in non-blocking mode.
+NS_INTERFACE_MAP_BEGIN(nsBaseContentStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::Close()
+{
+ return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Available(uint64_t *result)
+{
+ *result = 0;
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Read(char *buf, uint32_t count, uint32_t *result)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ *result = 0;
+
+ if (mStatus == NS_BASE_STREAM_CLOSED)
+ return NS_OK;
+
+ // No data yet
+ if (!IsClosed() && IsNonBlocking())
+ return NS_BASE_STREAM_WOULD_BLOCK;
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::IsNonBlocking(bool *result)
+{
+ *result = mNonBlocking;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIAsyncInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::CloseWithStatus(nsresult status)
+{
+ if (IsClosed())
+ return NS_OK;
+
+ NS_ENSURE_ARG(NS_FAILED(status));
+ mStatus = status;
+
+ DispatchCallback();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::AsyncWait(nsIInputStreamCallback *callback,
+ uint32_t flags, uint32_t requestedCount,
+ nsIEventTarget *target)
+{
+ // Our _only_ consumer is nsInputStreamPump, so we simplify things here by
+ // making assumptions about how we will be called.
+ NS_ASSERTION(target, "unexpected parameter");
+ NS_ASSERTION(flags == 0, "unexpected parameter");
+ NS_ASSERTION(requestedCount == 0, "unexpected parameter");
+
+#ifdef DEBUG
+ bool correctThread;
+ target->IsOnCurrentThread(&correctThread);
+ NS_ASSERTION(correctThread, "event target must be on the current thread");
+#endif
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ if (!mCallback)
+ return NS_OK;
+
+ // If we're already closed, then dispatch this callback immediately.
+ if (IsClosed()) {
+ DispatchCallback();
+ return NS_OK;
+ }
+
+ OnCallbackPending();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h
new file mode 100644
index 000000000..992c8733e
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseContentStream_h__
+#define nsBaseContentStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream is designed to be subclassed with the intention of being
+// used to satisfy the nsBaseChannel::OpenContentStream method.
+//
+// The subclass typically overrides the default Available, ReadSegments and
+// CloseWithStatus methods. By default, Read is implemented in terms of
+// ReadSegments, and Close is implemented in terms of CloseWithStatus. If
+// CloseWithStatus is overriden, then the subclass will usually want to call
+// the base class' CloseWithStatus method before returning.
+//
+// If the stream is non-blocking, then readSegments may return the exception
+// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is
+// not at the "end-of-file" or already closed. This error code must not be
+// returned from the Available implementation. When the caller receives this
+// error code, he may choose to call the stream's AsyncWait method, in which
+// case the base stream will have a non-null PendingCallback. When the stream
+// has data or encounters an error, it should be sure to dispatch a pending
+// callback if one exists (see DispatchCallback). The implementation of the
+// base stream's CloseWithStatus (and Close) method will ensure that any
+// pending callback is dispatched. It is the responsibility of the subclass
+// to ensure that the pending callback is dispatched when it wants to have its
+// ReadSegments method called again.
+
+class nsBaseContentStream : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsBaseContentStream(bool nonBlocking)
+ : mStatus(NS_OK)
+ , mNonBlocking(nonBlocking) {
+ }
+
+ nsresult Status() { return mStatus; }
+ bool IsNonBlocking() { return mNonBlocking; }
+ bool IsClosed() { return NS_FAILED(mStatus); }
+
+ // Called to test if the stream has a pending callback.
+ bool HasPendingCallback() { return mCallback != nullptr; }
+
+ // The current dispatch target (may be null) for the pending callback if any.
+ nsIEventTarget *CallbackTarget() { return mCallbackTarget; }
+
+ // Called to dispatch a pending callback. If there is no pending callback,
+ // then this function does nothing. Pass true to this function to cause the
+ // callback to occur asynchronously; otherwise, the callback will happen
+ // before this function returns.
+ void DispatchCallback(bool async = true);
+
+ // Helper function to make code more self-documenting.
+ void DispatchCallbackSync() { DispatchCallback(false); }
+
+protected:
+ virtual ~nsBaseContentStream() {}
+
+private:
+ // Called from the base stream's AsyncWait method when a pending callback
+ // is installed on the stream.
+ virtual void OnCallbackPending() {}
+
+private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsresult mStatus;
+ bool mNonBlocking;
+};
+
+#endif // nsBaseContentStream_h__
diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp
new file mode 100644
index 000000000..e67c3009b
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -0,0 +1,832 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsBufferedStreams.h"
+#include "nsStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include <algorithm>
+
+#ifdef DEBUG_brendan
+# define METERING
+#endif
+
+#ifdef METERING
+# include <stdio.h>
+# define METER(x) x
+# define MAX_BIG_SEEKS 20
+
+static struct {
+ uint32_t mSeeksWithinBuffer;
+ uint32_t mSeeksOutsideBuffer;
+ uint32_t mBufferReadUponSeek;
+ uint32_t mBufferUnreadUponSeek;
+ uint32_t mBytesReadFromBuffer;
+ uint32_t mBigSeekIndex;
+ struct {
+ int64_t mOldOffset;
+ int64_t mNewOffset;
+ } mBigSeek[MAX_BIG_SEEKS];
+} bufstats;
+#else
+# define METER(x) /* nothing */
+#endif
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedStream
+
+nsBufferedStream::nsBufferedStream()
+ : mBuffer(nullptr),
+ mBufferStartOffset(0),
+ mCursor(0),
+ mFillPoint(0),
+ mStream(nullptr),
+ mBufferDisabled(false),
+ mEOF(false),
+ mGetBufferCount(0)
+{
+}
+
+nsBufferedStream::~nsBufferedStream()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsBufferedStream, nsISeekableStream)
+
+nsresult
+nsBufferedStream::Init(nsISupports* stream, uint32_t bufferSize)
+{
+ NS_ASSERTION(stream, "need to supply a stream");
+ NS_ASSERTION(mStream == nullptr, "already inited");
+ mStream = stream;
+ NS_IF_ADDREF(mStream);
+ mBufferSize = bufferSize;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mBuffer = new (mozilla::fallible) char[bufferSize];
+ if (mBuffer == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+nsresult
+nsBufferedStream::Close()
+{
+ NS_IF_RELEASE(mStream);
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ mBufferSize = 0;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mFillPoint = 0;
+ }
+#ifdef METERING
+ {
+ static FILE *tfp;
+ if (!tfp) {
+ tfp = fopen("/tmp/bufstats", "w");
+ if (tfp)
+ setvbuf(tfp, nullptr, _IOLBF, 0);
+ }
+ if (tfp) {
+ fprintf(tfp, "seeks within buffer: %u\n",
+ bufstats.mSeeksWithinBuffer);
+ fprintf(tfp, "seeks outside buffer: %u\n",
+ bufstats.mSeeksOutsideBuffer);
+ fprintf(tfp, "buffer read on seek: %u\n",
+ bufstats.mBufferReadUponSeek);
+ fprintf(tfp, "buffer unread on seek: %u\n",
+ bufstats.mBufferUnreadUponSeek);
+ fprintf(tfp, "bytes read from buffer: %u\n",
+ bufstats.mBytesReadFromBuffer);
+ for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) {
+ fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n",
+ i,
+ bufstats.mBigSeek[i].mOldOffset,
+ bufstats.mBigSeek[i].mNewOffset);
+ }
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Seek(int32_t whence, int64_t offset)
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ // If the underlying stream isn't a random access store, then fail early.
+ // We could possibly succeed for the case where the seek position denotes
+ // something that happens to be read into the buffer, but that would make
+ // the failure data-dependent.
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t absPos = 0;
+ switch (whence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ absPos = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ absPos = mBufferStartOffset;
+ absPos += mCursor;
+ absPos += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ absPos = -1;
+ break;
+ default:
+ NS_NOTREACHED("bogus seek whence parameter");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Let mCursor point into the existing buffer if the new position is
+ // between the current cursor and the mFillPoint "fencepost" -- the
+ // client may never get around to a Read or Write after this Seek.
+ // Read and Write worry about flushing and filling in that event.
+ // But if we're at EOF, make sure to pass the seek through to the
+ // underlying stream, because it may have auto-closed itself and
+ // needs to reopen.
+ uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint && !mEOF) {
+ METER(bufstats.mSeeksWithinBuffer++);
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(bufstats.mSeeksOutsideBuffer++);
+ METER(bufstats.mBufferReadUponSeek += mCursor);
+ METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor);
+ rv = Flush();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ras->Seek(whence, offset);
+ if (NS_FAILED(rv)) return rv;
+
+ mEOF = false;
+
+ // Recompute whether the offset we're seeking to is in our buffer.
+ // Note that we need to recompute because Flush() might have
+ // changed mBufferStartOffset.
+ offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint) {
+ // It's safe to just set mCursor to offsetInBuffer. In particular, we
+ // want to avoid calling Fill() here since we already have the data that
+ // was seeked to and calling Fill() might auto-close our underlying
+ // stream in some cases.
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex].mOldOffset =
+ mBufferStartOffset + int64_t(mCursor));
+ const int64_t minus1 = -1;
+ if (absPos == minus1) {
+ // then we had the SEEK_END case, above
+ int64_t tellPos;
+ rv = ras->Tell(&tellPos);
+ mBufferStartOffset = tellPos;
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ mBufferStartOffset = absPos;
+ }
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex++].mNewOffset =
+ mBufferStartOffset);
+
+ mFillPoint = mCursor = 0;
+ return Fill();
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Tell(int64_t *result)
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t result64 = mBufferStartOffset;
+ result64 += mCursor;
+ *result = result64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::SetEOF()
+{
+ if (mStream == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ras->SetEOF();
+ if (NS_SUCCEEDED(rv))
+ mEOF = true;
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream)
+
+NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_BUFFEREDINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream,
+ nsIInputStream,
+ nsIBufferedInputStream,
+ nsISeekableStream,
+ nsIStreamBufferAccess)
+
+nsresult
+nsBufferedInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsBufferedInputStream* stream = new nsBufferedInputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize)
+{
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Close()
+{
+ nsresult rv1 = NS_OK, rv2;
+ if (mStream) {
+ rv1 = Source()->Close();
+ NS_RELEASE(mStream);
+ }
+ rv2 = nsBufferedStream::Close();
+ if (NS_FAILED(rv1)) return rv1;
+ return rv2;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Available(uint64_t *result)
+{
+ nsresult rv = NS_OK;
+ *result = 0;
+ if (mStream) {
+ rv = Source()->Available(result);
+ }
+ *result += (mFillPoint - mCursor);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Read(char * buf, uint32_t count, uint32_t *result)
+{
+ if (mBufferDisabled) {
+ if (!mStream) {
+ *result = 0;
+ return NS_OK;
+ }
+ nsresult rv = Source()->Read(buf, count, result);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferStartOffset += *result; // so nsBufferedStream::Tell works
+ if (*result == 0) {
+ mEOF = true;
+ }
+ }
+ return rv;
+ }
+
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ *result = 0;
+
+ if (!mStream)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ while (count > 0) {
+ uint32_t amt = std::min(count, mFillPoint - mCursor);
+ if (amt > 0) {
+ uint32_t read = 0;
+ rv = writer(this, closure, mBuffer + mCursor, *result, amt, &read);
+ if (NS_FAILED(rv)) {
+ // errors returned from the writer end here!
+ rv = NS_OK;
+ break;
+ }
+ *result += read;
+ count -= read;
+ mCursor += read;
+ }
+ else {
+ rv = Fill();
+ if (NS_FAILED(rv) || mFillPoint == mCursor)
+ break;
+ }
+ }
+ return (*result > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ if (mStream)
+ return Source()->IsNonBlocking(aNonBlocking);
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Fill()
+{
+ if (mBufferDisabled)
+ return NS_OK;
+ NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+ int32_t rem = int32_t(mFillPoint - mCursor);
+ if (rem > 0) {
+ // slide the remainder down to the start of the buffer
+ // |<------------->|<--rem-->|<--->|
+ // b c f s
+ memcpy(mBuffer, mBuffer + mCursor, rem);
+ }
+ mBufferStartOffset += mCursor;
+ mFillPoint = rem;
+ mCursor = 0;
+
+ uint32_t amt;
+ rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt);
+ if (NS_FAILED(rv)) return rv;
+
+ if (amt == 0)
+ mEOF = true;
+
+ mFillPoint += amt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0)
+ return nullptr;
+
+ if (mBufferDisabled)
+ return nullptr;
+
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mFillPoint - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Fill()))
+ return nullptr;
+ buf = mBuffer + mCursor;
+ rem = mFillPoint - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem)
+ return nullptr;
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem)
+ return nullptr;
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0)
+ return;
+
+ NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch");
+ mCursor += aLength;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::DisableBuffering()
+{
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0)
+ return NS_ERROR_UNEXPECTED;
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::EnableBuffering()
+{
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetUnbufferedStream(nsISupports* *aStream)
+{
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+
+ *aStream = mStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+void
+nsBufferedInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ BufferedInputStreamParams params;
+
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ SerializeInputStream(stream, wrappedParams, aFileDescriptors);
+
+ params.optionalStream() = wrappedParams;
+ }
+ else {
+ params.optionalStream() = mozilla::void_t();
+ }
+
+ params.bufferSize() = mBufferSize;
+
+ aParams = params;
+}
+
+bool
+nsBufferedInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ const OptionalInputStreamParams& wrappedParams = params.optionalStream();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
+ stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
+ aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+ }
+ else {
+ NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
+ "Unknown type for OptionalInputStreamParams!");
+ }
+
+ nsresult rv = Init(stream, params.bufferSize());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsBufferedInputStream::ExpectedSerializedLength()
+{
+ nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream);
+ if (stream) {
+ return stream->ExpectedSerializedLength();
+ }
+ return Nothing();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedOutputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+// non-nullness of mSafeStream.
+NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+nsresult
+nsBufferedOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsBufferedOutputStream* stream = new nsBufferedOutputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize)
+{
+ // QI stream to an nsISafeOutputStream, to see if we should support it
+ mSafeStream = do_QueryInterface(stream);
+
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Close()
+{
+ nsresult rv1, rv2 = NS_OK, rv3;
+ rv1 = Flush();
+ // If we fail to Flush all the data, then we close anyway and drop the
+ // remaining data in the buffer. We do this because it's what Unix does
+ // for fclose and close. However, we report the error from Flush anyway.
+ if (mStream) {
+ rv2 = Sink()->Close();
+ NS_RELEASE(mStream);
+ }
+ rv3 = nsBufferedStream::Close();
+ if (NS_FAILED(rv1)) return rv1;
+ if (NS_FAILED(rv2)) return rv2;
+ return rv3;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = NS_OK;
+ uint32_t written = 0;
+ while (count > 0) {
+ uint32_t amt = std::min(count, mBufferSize - mCursor);
+ if (amt > 0) {
+ memcpy(mBuffer + mCursor, buf + written, amt);
+ written += amt;
+ count -= amt;
+ mCursor += amt;
+ if (mFillPoint < mCursor)
+ mFillPoint = mCursor;
+ }
+ else {
+ NS_ASSERTION(mFillPoint, "iloop in nsBufferedOutputStream::Write!");
+ rv = Flush();
+ if (NS_FAILED(rv)) break;
+ }
+ }
+ *result = written;
+ return (written > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Flush()
+{
+ nsresult rv;
+ uint32_t amt;
+ if (!mStream) {
+ // Stream already cancelled/flushed; probably because of previous error.
+ return NS_OK;
+ }
+ rv = Sink()->Write(mBuffer, mFillPoint, &amt);
+ if (NS_FAILED(rv)) return rv;
+ mBufferStartOffset += amt;
+ if (amt == mFillPoint) {
+ mFillPoint = mCursor = 0;
+ return NS_OK; // flushed everything
+ }
+
+ // slide the remainder down to the start of the buffer
+ // |<-------------->|<---|----->|
+ // b a c s
+ uint32_t rem = mFillPoint - amt;
+ memmove(mBuffer, mBuffer + amt, rem);
+ mFillPoint = mCursor = rem;
+ return NS_ERROR_FAILURE; // didn't flush all
+}
+
+// nsISafeOutputStream
+NS_IMETHODIMP
+nsBufferedOutputStream::Finish()
+{
+ // flush the stream, to write out any buffered data...
+ nsresult rv = nsBufferedOutputStream::Flush();
+ if (NS_FAILED(rv))
+ NS_WARNING("failed to flush buffered data! possible dataloss");
+
+ // ... and finish the underlying stream...
+ if (NS_SUCCEEDED(rv))
+ rv = mSafeStream->Finish();
+ else
+ Sink()->Close();
+
+ // ... and close the buffered stream, so any further attempts to flush/close
+ // the buffered stream won't cause errors.
+ nsBufferedStream::Close();
+
+ return rv;
+}
+
+static nsresult
+nsReadFromInputStream(nsIOutputStream* outStr,
+ void* closure,
+ char* toRawSegment,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *readCount)
+{
+ nsIInputStream* fromStream = (nsIInputStream*)closure;
+ return fromStream->Read(toRawSegment, count, readCount);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return WriteSegments(nsReadFromInputStream, inStr, count, _retval);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ *_retval = 0;
+ nsresult rv;
+ while (count > 0) {
+ uint32_t left = std::min(count, mBufferSize - mCursor);
+ if (left == 0) {
+ rv = Flush();
+ if (NS_FAILED(rv))
+ return (*_retval > 0) ? NS_OK : rv;
+
+ continue;
+ }
+
+ uint32_t read = 0;
+ rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read);
+
+ if (NS_FAILED(rv)) // If we have written some data, return ok
+ return (*_retval > 0) ? NS_OK : rv;
+ mCursor += read;
+ *_retval += read;
+ count -= read;
+ mFillPoint = std::max(mFillPoint, mCursor);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ if (mStream)
+ return Sink()->IsNonBlocking(aNonBlocking);
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0)
+ return nullptr;
+
+ if (mBufferDisabled)
+ return nullptr;
+
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mBufferSize - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Flush()))
+ return nullptr;
+ buf = mBuffer + mCursor;
+ rem = mBufferSize - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem)
+ return nullptr;
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem)
+ return nullptr;
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0)
+ return;
+
+ NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch");
+ mCursor += aLength;
+ if (mFillPoint < mCursor)
+ mFillPoint = mCursor;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::DisableBuffering()
+{
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0)
+ return NS_ERROR_UNEXPECTED;
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ nsresult rv = Flush();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::EnableBuffering()
+{
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetUnbufferedStream(nsISupports* *aStream)
+{
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ if (mFillPoint) {
+ nsresult rv = Flush();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ *aStream = mStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+#undef METER
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h
new file mode 100644
index 000000000..93a770beb
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBufferedStreams_h__
+#define nsBufferedStreams_h__
+
+#include "nsIBufferedStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedStream : public nsISeekableStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+
+ nsBufferedStream();
+
+ nsresult Close();
+
+protected:
+ virtual ~nsBufferedStream();
+
+ nsresult Init(nsISupports* stream, uint32_t bufferSize);
+ NS_IMETHOD Fill() = 0;
+ NS_IMETHOD Flush() = 0;
+
+ uint32_t mBufferSize;
+ char* mBuffer;
+
+ // mBufferStartOffset is the offset relative to the start of mStream.
+ int64_t mBufferStartOffset;
+
+ // mCursor is the read cursor for input streams, or write cursor for
+ // output streams, and is relative to mBufferStartOffset.
+ uint32_t mCursor;
+
+ // mFillPoint is the amount available in the buffer for input streams,
+ // or the high watermark of bytes written into the buffer, and therefore
+ // is relative to mBufferStartOffset.
+ uint32_t mFillPoint;
+
+ nsISupports* mStream; // cast to appropriate subclass
+
+ bool mBufferDisabled;
+ bool mEOF; // True if mStream is at EOF
+ uint8_t mGetBufferCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedInputStream : public nsBufferedStream,
+ public nsIBufferedInputStream,
+ public nsIStreamBufferAccess,
+ public nsIIPCSerializableInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIBUFFEREDINPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsBufferedInputStream() : nsBufferedStream() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ nsIInputStream* Source() {
+ return (nsIInputStream*)mStream;
+ }
+
+protected:
+ virtual ~nsBufferedInputStream() {}
+
+ NS_IMETHOD Fill() override;
+ NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedOutputStream final : public nsBufferedStream,
+ public nsISafeOutputStream,
+ public nsIBufferedOutputStream,
+ public nsIStreamBufferAccess
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+ NS_DECL_NSIBUFFEREDOUTPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+
+ nsBufferedOutputStream() : nsBufferedStream() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+ nsIOutputStream* Sink() {
+ return (nsIOutputStream*)mStream;
+ }
+
+protected:
+ virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); }
+
+ NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams
+
+ nsCOMPtr<nsISafeOutputStream> mSafeStream; // QI'd from mStream
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsBufferedStreams_h__
diff --git a/netwerk/base/nsChannelClassifier.cpp b/netwerk/base/nsChannelClassifier.cpp
new file mode 100644
index 000000000..6b9f9ede3
--- /dev/null
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsChannelClassifier.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsICacheEntry.h"
+#include "nsICachingChannel.h"
+#include "nsIChannel.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIParentChannel.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecureBrowserUI.h"
+#include "nsISecurityEventSink.h"
+#include "nsIURL.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// MOZ_LOG=nsChannelClassifier:5
+//
+static LazyLogModule gChannelClassifierLog("nsChannelClassifier");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS(nsChannelClassifier,
+ nsIURIClassifierCallback)
+
+nsChannelClassifier::nsChannelClassifier()
+ : mIsAllowListed(false),
+ mSuspendedChannel(false)
+{
+}
+
+nsresult
+nsChannelClassifier::ShouldEnableTrackingProtection(nsIChannel *aChannel,
+ bool *result)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ NS_ENSURE_ARG(result);
+ *result = false;
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext || !(loadContext->UseTrackingProtection())) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI\n", this));
+ }
+
+ nsCOMPtr<nsIURI> chanURI;
+ rv = aChannel->GetURI(getter_AddRefs(chanURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Third party checks don't work for chrome:// URIs in mochitests, so just
+ // default to isThirdParty = true. We check isThirdPartyWindow to expand
+ // the list of domains that are considered first party (e.g., if
+ // facebook.com includes an iframe from fatratgames.com, all subsources
+ // included in that iframe are considered third-party with
+ // isThirdPartyChannel, even if they are not third-party w.r.t.
+ // facebook.com), and isThirdPartyChannel to prevent top-level navigations
+ // from being detected as third-party.
+ bool isThirdPartyChannel = true;
+ bool isThirdPartyWindow = true;
+ thirdPartyUtil->IsThirdPartyURI(chanURI, topWinURI, &isThirdPartyWindow);
+ thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &isThirdPartyChannel);
+ if (!isThirdPartyWindow || !isThirdPartyChannel) {
+ *result = false;
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Skipping tracking protection checks "
+ "for first party or top-level load channel[%p] with uri %s",
+ this, aChannel, chanURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char ALLOWLIST_EXAMPLE_PREF[] = "channelclassifier.allowlist_example";
+ if (!topWinURI && Preferences::GetBool(ALLOWLIST_EXAMPLE_PREF, false)) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this));
+ rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"),
+ nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Take the host/port portion so we can allowlist by site. Also ignore the
+ // scheme, since users who put sites on the allowlist probably don't expect
+ // allowlisting to depend on scheme.
+ nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv);
+ if (NS_FAILED(rv)) {
+ return rv; // normal for some loads, no need to print a warning
+ }
+
+ nsCString escaped(NS_LITERAL_CSTRING("https://"));
+ nsAutoCString temp;
+ rv = url->GetHostPort(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ escaped.Append(temp);
+
+ // Stuff the whole thing back into a URI for the permission manager.
+ rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
+ rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (permissions == nsIPermissionManager::ALLOW_ACTION) {
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] for %s", this,
+ aChannel, escaped.get()));
+ mIsAllowListed = true;
+ *result = false;
+ } else {
+ *result = true;
+ }
+
+ // In Private Browsing Mode we also check against an in-memory list.
+ if (NS_UsePrivateBrowsing(aChannel)) {
+ nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist =
+ do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ mIsAllowListed = true;
+ LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] in PBM for %s",
+ this, aChannel, escaped.get()));
+ }
+
+ *result = !exists;
+ }
+
+ // Tracking protection will be enabled so return without updating
+ // the security state. If any channels are subsequently cancelled
+ // (page elements blocked) the state will be then updated.
+ if (*result) {
+ if (LOG_ENABLED()) {
+ LOG(("nsChannelClassifier[%p]: Enabling tracking protection checks on "
+ "channel[%p] with uri %s for toplevel window %s", this, aChannel,
+ chanURI->GetSpecOrDefault().get(),
+ topWinURI->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+ }
+
+ // Tracking protection will be disabled so update the security state
+ // of the document and fire a secure change event. If we can't get the
+ // window for the channel, then the shield won't show up so we can't send
+ // an event to the securityUI anyway.
+ return NotifyTrackingProtectionDisabled(aChannel);
+}
+
+// static
+nsresult
+nsChannelClassifier::NotifyTrackingProtectionDisabled(nsIChannel *aChannel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(aChannel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request.
+ // Tell the child process channel to do this instead.
+ parentChannel->NotifyTrackingProtectionDisabled();
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentLoaded(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ return NS_OK;
+}
+
+void
+nsChannelClassifier::Start(nsIChannel *aChannel)
+{
+ mChannel = aChannel;
+
+ nsresult rv = StartInternal();
+ if (NS_FAILED(rv)) {
+ // If we aren't getting a callback for any reason, assume a good verdict and
+ // make sure we resume the channel if necessary.
+ OnClassifyComplete(NS_OK);
+ }
+}
+
+nsresult
+nsChannelClassifier::StartInternal()
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't bother to run the classifier on a load that has already failed.
+ // (this might happen after a redirect)
+ nsresult status;
+ mChannel->GetStatus(&status);
+ if (NS_FAILED(status))
+ return status;
+
+ // Don't bother to run the classifier on a cached load that was
+ // previously classified as good.
+ if (HasBeenClassified(mChannel)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't bother checking certain types of URIs.
+ bool hasFlags;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &hasFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasFlags) return NS_ERROR_UNEXPECTED;
+
+ // Skip whitelisted hostnames.
+ nsAutoCString whitelisted;
+ Preferences::GetCString("urlclassifier.skipHostnames", &whitelisted);
+ if (!whitelisted.IsEmpty()) {
+ ToLowerCase(whitelisted);
+ LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s",
+ this, whitelisted.get()));
+ if (IsHostnameWhitelisted(uri, whitelisted)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED ||
+ rv == NS_ERROR_NOT_AVAILABLE) {
+ // no URI classifier, ignore this failure.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool expectCallback;
+ bool trackingProtectionEnabled = false;
+ (void)ShouldEnableTrackingProtection(mChannel, &trackingProtectionEnabled);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> principalURI;
+ principal->GetURI(getter_AddRefs(principalURI));
+ LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel with "
+ "uri %s", this, principalURI->GetSpecOrDefault().get(),
+ uri->GetSpecOrDefault().get()));
+ }
+ rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this,
+ &expectCallback);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (expectCallback) {
+ // Suspend the channel, it will be resumed when we get the classifier
+ // callback.
+ rv = mChannel->Suspend();
+ if (NS_FAILED(rv)) {
+ // Some channels (including nsJSChannel) fail on Suspend. This
+ // shouldn't be fatal, but will prevent malware from being
+ // blocked on these channels.
+ LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
+ return rv;
+ }
+
+ mSuspendedChannel = true;
+ LOG(("nsChannelClassifier[%p]: suspended channel %p",
+ this, mChannel.get()));
+ } else {
+ LOG(("nsChannelClassifier[%p]: not expecting callback", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsChannelClassifier::IsHostnameWhitelisted(nsIURI *aUri,
+ const nsACString &aWhitelisted)
+{
+ nsAutoCString host;
+ nsresult rv = aUri->GetHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+ ToLowerCase(host);
+
+ nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& token = tokenizer.nextToken();
+ if (token.Equals(host)) {
+ LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)",
+ this, host.get()));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Note in the cache entry that this URL was classified, so that future
+// cached loads don't need to be checked.
+void
+nsChannelClassifier::MarkEntryClassified(nsresult status)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Don't cache tracking classifications because we support allowlisting.
+ if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) {
+ return;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString errorName;
+ GetErrorName(status, errorName);
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s",
+ errorName.get(), spec.get()));
+ }
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
+ if (!cachingChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return;
+ }
+
+ cacheEntry->SetMetaDataElement("necko:classified",
+ NS_SUCCEEDED(status) ? "1" : nullptr);
+}
+
+bool
+nsChannelClassifier::HasBeenClassified(nsIChannel *aChannel)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsICachingChannel> cachingChannel =
+ do_QueryInterface(aChannel);
+ if (!cachingChannel) {
+ return false;
+ }
+
+ // Only check the tag if we are loading from the cache without
+ // validation.
+ bool fromCache;
+ if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
+ return false;
+ }
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (!cacheToken) {
+ return false;
+ }
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ do_QueryInterface(cacheToken);
+ if (!cacheEntry) {
+ return false;
+ }
+
+ nsXPIDLCString tag;
+ cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
+ return tag.EqualsLiteral("1");
+}
+
+//static
+bool
+nsChannelClassifier::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel)
+{
+ nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
+ if (!channelLoadInfo || !docURI) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelLoadingPrincipal = channelLoadInfo->LoadingPrincipal();
+ if (!channelLoadingPrincipal) {
+ // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top level
+ // loads should not be blocked by Tracking Protection, so we will return
+ // false
+ return false;
+ }
+ nsCOMPtr<nsIURI> channelLoadingURI;
+ channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI));
+ if (!channelLoadingURI) {
+ return false;
+ }
+ bool equals = false;
+ nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals);
+ return NS_SUCCEEDED(rv) && equals;
+}
+
+// static
+nsresult
+nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel)
+{
+ // Can be called in EITHER the parent or child process.
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ NS_QueryNotificationCallbacks(channel, parentChannel);
+ if (parentChannel) {
+ // This channel is a parent-process proxy for a child process request. The
+ // actual channel will be notified via the status passed to
+ // nsIRequest::Cancel and do this for us.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ auto* pwin = nsPIDOMWindowOuter::From(win);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_OK);
+
+ // This event might come after the user has navigated to another page.
+ // To prevent showing the TrackingProtection UI on the wrong page, we need to
+ // check that the loading URI for the channel is the same as the URI currently
+ // loaded in the document.
+ if (!SameLoadingURI(doc, channel)) {
+ return NS_OK;
+ }
+
+ // Notify nsIWebProgressListeners of this security event.
+ // Can be used to change the UI state.
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ uint32_t state = 0;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ docShell->GetSecurityUI(getter_AddRefs(securityUI));
+ if (!securityUI) {
+ return NS_OK;
+ }
+ doc->SetHasTrackingContentBlocked(true);
+ securityUI->GetState(&state);
+ state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+ eventSink->OnSecurityChange(nullptr, state);
+
+ // Log a warning to the web console.
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault());
+ const char16_t* params[] = { spec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Tracking Protection"),
+ doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "TrackingUriBlocked",
+ params, ArrayLength(params));
+
+ return NS_OK;
+}
+
+nsresult
+nsChannelClassifier::IsTrackerWhitelisted()
+{
+ nsresult rv;
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString tables;
+ Preferences::GetCString("urlclassifier.trackingWhitelistTable", &tables);
+
+ if (tables.IsEmpty()) {
+ LOG(("nsChannelClassifier[%p]:IsTrackerWhitelisted whitelist disabled",
+ this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> topWinURI;
+ rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!topWinURI) {
+ LOG(("nsChannelClassifier[%p]: No window URI", this));
+ return NS_ERROR_TRACKING_URI;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> chanPrincipal;
+ rv = securityManager->GetChannelURIPrincipal(mChannel,
+ getter_AddRefs(chanPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Craft a whitelist URL like "toplevel.page/?resource=third.party.domain"
+ nsAutoCString pageHostname, resourceDomain;
+ rv = topWinURI->GetHost(pageHostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = chanPrincipal->GetBaseDomain(resourceDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString whitelistEntry = NS_LITERAL_CSTRING("http://") +
+ pageHostname + NS_LITERAL_CSTRING("/?resource=") + resourceDomain;
+ LOG(("nsChannelClassifier[%p]: Looking for %s in the whitelist",
+ this, whitelistEntry.get()));
+
+ nsCOMPtr<nsIURI> whitelistURI;
+ rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check whether or not the tracker is in the entity whitelist
+ nsAutoCString results;
+ rv = uriClassifier->ClassifyLocalWithTables(whitelistURI, tables, results);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!results.IsEmpty()) {
+ return NS_OK; // found it on the whitelist, must not be blocked
+ }
+
+ LOG(("nsChannelClassifier[%p]: %s is not in the whitelist",
+ this, whitelistEntry.get()));
+ return NS_ERROR_TRACKING_URI;
+}
+
+NS_IMETHODIMP
+nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode)
+{
+ // Should only be called in the parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (aErrorCode == NS_ERROR_TRACKING_URI &&
+ NS_SUCCEEDED(IsTrackerWhitelisted())) {
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found "
+ "in whitelist so we won't block it", this));
+ aErrorCode = NS_OK;
+ }
+
+ if (mSuspendedChannel) {
+ nsAutoCString errorName;
+ if (LOG_ENABLED()) {
+ GetErrorName(aErrorCode, errorName);
+ LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)",
+ this, errorName.get()));
+ }
+ MarkEntryClassified(aErrorCode);
+
+ if (NS_FAILED(aErrorCode)) {
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s "
+ "with error code %s", this, mChannel.get(),
+ uri->GetSpecOrDefault().get(), errorName.get()));
+ }
+
+ // Channel will be cancelled (page element blocked) due to tracking.
+ // Do update the security state of the document and fire a security
+ // change event.
+ if (aErrorCode == NS_ERROR_TRACKING_URI) {
+ SetBlockedTrackingContent(mChannel);
+ }
+
+ mChannel->Cancel(aErrorCode);
+ }
+ LOG(("nsChannelClassifier[%p]: resuming channel %p from "
+ "OnClassifyComplete", this, mChannel.get()));
+ mChannel->Resume();
+ }
+
+ mChannel = nullptr;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsChannelClassifier.h b/netwerk/base/nsChannelClassifier.h
new file mode 100644
index 000000000..20575f3c1
--- /dev/null
+++ b/netwerk/base/nsChannelClassifier.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChannelClassifier_h__
+#define nsChannelClassifier_h__
+
+#include "nsIURIClassifier.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+class nsIHttpChannelInternal;
+class nsIDocument;
+
+namespace mozilla {
+namespace net {
+
+class nsChannelClassifier final : public nsIURIClassifierCallback
+{
+public:
+ nsChannelClassifier();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURICLASSIFIERCALLBACK
+
+ // Calls nsIURIClassifier.Classify with the principal of the given channel,
+ // and cancels the channel on a bad verdict.
+ void Start(nsIChannel *aChannel);
+ // Whether or not tracking protection should be enabled on this channel.
+ nsresult ShouldEnableTrackingProtection(nsIChannel *aChannel, bool *result);
+
+private:
+ // True if the channel is on the allow list.
+ bool mIsAllowListed;
+ // True if the channel has been suspended.
+ bool mSuspendedChannel;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ ~nsChannelClassifier() {}
+ // Caches good classifications for the channel principal.
+ void MarkEntryClassified(nsresult status);
+ bool HasBeenClassified(nsIChannel *aChannel);
+ // Helper function so that we ensure we call ContinueBeginConnect once
+ // Start is called. Returns NS_OK if and only if we will get a callback
+ // from the classifier service.
+ nsresult StartInternal();
+ // Helper function to check a tracking URI against the whitelist
+ nsresult IsTrackerWhitelisted();
+ // Helper function to check a URI against the hostname whitelist
+ bool IsHostnameWhitelisted(nsIURI *aUri, const nsACString &aWhitelisted);
+ // Checks that the channel was loaded by the URI currently loaded in aDoc
+ static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel);
+
+public:
+ // If we are blocking tracking content, update the corresponding flag in
+ // the respective docshell and call nsISecurityEventSink::onSecurityChange.
+ static nsresult SetBlockedTrackingContent(nsIChannel *channel);
+ static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp
new file mode 100644
index 000000000..e09315ed1
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsIURI.h"
+
+static nsIDNSService *sDNSService = nullptr;
+
+nsresult
+nsDNSPrefetch::Initialize(nsIDNSService *aDNSService)
+{
+ NS_IF_RELEASE(sDNSService);
+ sDNSService = aDNSService;
+ NS_IF_ADDREF(sDNSService);
+ return NS_OK;
+}
+
+nsresult
+nsDNSPrefetch::Shutdown()
+{
+ NS_IF_RELEASE(sDNSService);
+ return NS_OK;
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI *aURI,
+ nsIDNSListener *aListener,
+ bool storeTiming)
+ : mStoreTiming(storeTiming)
+ , mListener(do_GetWeakReference(aListener))
+{
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsresult
+nsDNSPrefetch::Prefetch(uint16_t flags)
+{
+ if (mHostname.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!sDNSService)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ if (mStoreTiming)
+ mStartTimestamp = mozilla::TimeStamp::Now();
+ // If AsyncResolve fails, for example because prefetching is disabled,
+ // then our timing will be useless. However, in such a case,
+ // mEndTimestamp will be a null timestamp and callers should check
+ // TimingsValid() before using the timing.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ return sDNSService->AsyncResolve(mHostname,
+ flags | nsIDNSService::RESOLVE_SPECULATE,
+ this, mainThread,
+ getter_AddRefs(tmpOutstanding));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchLow(bool refreshDNS)
+{
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchMedium(bool refreshDNS)
+{
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult
+nsDNSPrefetch::PrefetchHigh(bool refreshDNS)
+{
+ return Prefetch(refreshDNS ?
+ nsIDNSService::RESOLVE_BYPASS_CACHE : 0);
+}
+
+
+NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener)
+
+NS_IMETHODIMP
+nsDNSPrefetch::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+ if (mStoreTiming) {
+ mEndTimestamp = mozilla::TimeStamp::Now();
+ }
+ nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ listener->OnLookupComplete(request, rec, status);
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h
new file mode 100644
index 000000000..3ad6d4bf0
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDNSPrefetch_h___
+#define nsDNSPrefetch_h___
+
+#include "nsWeakReference.h"
+#include "nsString.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+
+#include "nsIDNSListener.h"
+
+class nsIURI;
+class nsIDNSService;
+
+class nsDNSPrefetch final : public nsIDNSListener
+{
+ ~nsDNSPrefetch() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ nsDNSPrefetch(nsIURI *aURI, nsIDNSListener *aListener, bool storeTiming);
+ bool TimingsValid() const {
+ return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull();
+ }
+ // Only use the two timings if TimingsValid() returns true
+ const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; }
+ const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; }
+
+ static nsresult Initialize(nsIDNSService *aDNSService);
+ static nsresult Shutdown();
+
+ // Call one of the following methods to start the Prefetch.
+ nsresult PrefetchHigh(bool refreshDNS = false);
+ nsresult PrefetchMedium(bool refreshDNS = false);
+ nsresult PrefetchLow(bool refreshDNS = false);
+
+private:
+ nsCString mHostname;
+ bool mStoreTiming;
+ mozilla::TimeStamp mStartTimestamp;
+ mozilla::TimeStamp mEndTimestamp;
+ nsWeakPtr mListener;
+
+ nsresult Prefetch(uint16_t flags);
+};
+
+#endif
diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp
new file mode 100644
index 000000000..87a57fd57
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+
+ The converts a filesystem directory into an "HTTP index" stream per
+ Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ */
+
+#include "nsEscape.h"
+#include "nsDirectoryIndexStream.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "nsISimpleEnumerator.h"
+#ifdef THREADSAFE_I18N
+#include "nsCollationCID.h"
+#include "nsICollation.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#endif
+#include "nsIFile.h"
+#include "nsURLHelper.h"
+#include "nsNativeCharsetUtils.h"
+
+// NOTE: This runs on the _file transport_ thread.
+// The problem is that now that we're actually doing something with the data,
+// we want to do stuff like i18n sorting. However, none of the collation stuff
+// is threadsafe.
+// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
+// behaviour, though. See bug 99382.
+// When this is fixed, #define THREADSAFE_I18N to get this code working
+
+//#define THREADSAFE_I18N
+
+using namespace mozilla;
+static LazyLogModule gLog("nsDirectoryIndexStream");
+
+nsDirectoryIndexStream::nsDirectoryIndexStream()
+ : mOffset(0), mStatus(NS_OK), mPos(0)
+{
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: created", this));
+}
+
+static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData)
+{
+ if (!NS_IsNativeUTF8()) {
+ // don't check for errors, because we can't report them anyway
+ nsAutoString name1, name2;
+ aElement1->GetLeafName(name1);
+ aElement2->GetLeafName(name2);
+
+ // Note - we should do the collation to do sorting. Why don't we?
+ // Because that is _slow_. Using TestProtocols to list file:///dev/
+ // goes from 3 seconds to 22. (This may be why nsXULSortService is
+ // so slow as well).
+ // Does this have bad effects? Probably, but since nsXULTree appears
+ // to use the raw RDF literal value as the sort key (which ammounts to an
+ // strcmp), it won't be any worse, I think.
+ // This could be made faster, by creating the keys once,
+ // but CompareString could still be smarter - see bug 99383 - bbaetz
+ // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
+ // threadsafe so that this matters, we'd have to pass through a
+ // struct { nsIFile*, uint8_t* } with the pre-calculated key.
+ return Compare(name1, name2);
+ }
+
+ nsAutoCString name1, name2;
+ aElement1->GetNativeLeafName(name1);
+ aElement2->GetNativeLeafName(name2);
+
+ return Compare(name1, name2);
+}
+
+nsresult
+nsDirectoryIndexStream::Init(nsIFile* aDir)
+{
+ nsresult rv;
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ NS_PRECONDITION(isDir, "not a directory");
+ if (!isDir)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ aDir->GetNativePath(path);
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: initialized on %s",
+ this, path.get()));
+ }
+
+ // Sigh. We have to allocate on the heap because there are no
+ // assignment operators defined.
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now lets sort, because clients expect it that way
+ // XXX - should we do so here, or when the first item is requested?
+ // XXX - use insertion sort instead?
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+ if (file)
+ mArray.AppendObject(file); // addrefs
+ }
+ }
+
+#ifdef THREADSAFE_I18N
+ nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILocale> locale;
+ rv = ls->GetApplicationLocale(getter_AddRefs(locale));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICollationFactory> cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID,
+ &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICollation> coll;
+ rv = cf->CreateCollation(locale, getter_AddRefs(coll));
+ if (NS_FAILED(rv)) return rv;
+
+ mArray.Sort(compare, coll);
+#else
+ mArray.Sort(compare, nullptr);
+#endif
+
+ mBuf.AppendLiteral("300: ");
+ nsAutoCString url;
+ rv = net_GetURLSpecFromFile(aDir, url);
+ if (NS_FAILED(rv)) return rv;
+ mBuf.Append(url);
+ mBuf.Append('\n');
+
+ mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ return NS_OK;
+}
+
+nsDirectoryIndexStream::~nsDirectoryIndexStream()
+{
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: destroyed", this));
+}
+
+nsresult
+nsDirectoryIndexStream::Create(nsIFile* aDir, nsIInputStream** aResult)
+{
+ RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream();
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = result->Init(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream)
+
+// The below routines are proxied to the UI thread!
+NS_IMETHODIMP
+nsDirectoryIndexStream::Close()
+{
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Available(uint64_t* aLength)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ // If there's data in our buffer, use that
+ if (mOffset < (int32_t)mBuf.Length()) {
+ *aLength = mBuf.Length() - mOffset;
+ return NS_OK;
+ }
+
+ // Returning one byte is not ideal, but good enough
+ *aLength = (mPos < mArray.Count()) ? 1 : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ uint32_t nread = 0;
+
+ // If anything is enqueued (or left-over) in mBuf, then feed it to
+ // the reader first.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+
+ // Room left?
+ if (aCount > 0) {
+ mOffset = 0;
+ mBuf.Truncate();
+
+ // Okay, now we'll suck stuff off of our iterator into the mBuf...
+ while (uint32_t(mBuf.Length()) < aCount) {
+ bool more = mPos < mArray.Count();
+ if (!more) break;
+
+ // don't addref, for speed - an addref happened when it
+ // was placed in the array, so it's not going to go stale
+ nsIFile* current = mArray.ObjectAt(mPos);
+ ++mPos;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ current->GetNativePath(path);
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: iterated %s",
+ this, path.get()));
+ }
+
+ // rjc: don't return hidden files/directories!
+ // bbaetz: why not?
+ nsresult rv;
+#ifndef XP_UNIX
+ bool hidden = false;
+ current->IsHidden(&hidden);
+ if (hidden) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
+ this));
+ continue;
+ }
+#endif
+
+ int64_t fileSize = 0;
+ current->GetFileSize( &fileSize );
+
+ PRTime fileInfoModifyTime = 0;
+ current->GetLastModifiedTime( &fileInfoModifyTime );
+ fileInfoModifyTime *= PR_USEC_PER_MSEC;
+
+ mBuf.AppendLiteral("201: ");
+
+ // The "filename" field
+ if (!NS_IsNativeUTF8()) {
+ nsAutoString leafname;
+ rv = current->GetLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ } else {
+ nsAutoCString leafname;
+ rv = current->GetNativeLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(leafname, escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ }
+
+ // The "content-length" field
+ mBuf.AppendInt(fileSize, 10);
+ mBuf.Append(' ');
+
+ // The "last-modified" field
+ PRExplodedTime tm;
+ PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
+ mBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ bool isFile = true;
+ current->IsFile(&isFile);
+ if (isFile) {
+ mBuf.AppendLiteral("FILE ");
+ }
+ else {
+ bool isDir;
+ rv = current->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (isDir) {
+ mBuf.AppendLiteral("DIRECTORY ");
+ }
+ else {
+ bool isLink;
+ rv = current->IsSymlink(&isLink);
+ if (NS_FAILED(rv)) return rv;
+ if (isLink) {
+ mBuf.AppendLiteral("SYMBOLIC-LINK ");
+ }
+ }
+ }
+
+ mBuf.Append('\n');
+ }
+
+ // ...and once we've either run out of directory entries, or
+ // filled up the buffer, then we'll push it to the reader.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+ }
+
+ *aReadCount = nread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h
new file mode 100644
index 000000000..5403c7af2
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryIndexStream_h__
+#define nsDirectoryIndexStream_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsString.h"
+#include "nsIInputStream.h"
+#include "nsCOMArray.h"
+
+class nsIFile;
+
+class nsDirectoryIndexStream final : public nsIInputStream
+{
+private:
+ nsCString mBuf;
+ int32_t mOffset;
+ nsresult mStatus;
+
+ int32_t mPos; // position within mArray
+ nsCOMArray<nsIFile> mArray; // file objects within the directory
+
+ nsDirectoryIndexStream();
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ nsresult Init(nsIFile* aDir);
+ ~nsDirectoryIndexStream();
+
+public:
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ static nsresult
+ Create(nsIFile* aDir, nsIInputStream** aStreamResult);
+
+ // nsISupportsInterface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIInputStream interface
+ NS_DECL_NSIINPUTSTREAM
+};
+
+#endif // nsDirectoryIndexStream_h__
+
diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp
new file mode 100644
index 000000000..6248be8f1
--- /dev/null
+++ b/netwerk/base/nsDownloader.cpp
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDownloader.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+
+nsDownloader::~nsDownloader()
+{
+ if (mLocation && mLocationIsTemp) {
+ // release the sink first since it may still hold an open file
+ // descriptor to mLocation. this needs to happen before the
+ // file can be removed otherwise the Remove call will fail.
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ nsresult rv = mLocation->Remove(false);
+ if (NS_FAILED(rv))
+ NS_ERROR("unable to remove temp file");
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDownloader,
+ nsIDownloader,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsDownloader::Init(nsIDownloadObserver *observer, nsIFile *location)
+{
+ mObserver = observer;
+ mLocation = location;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ nsresult rv;
+ if (!mLocation) {
+ nsCOMPtr<nsIFile> location;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[13];
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf+8, ".tmp", 5);
+ rv = location->AppendNative(nsDependentCString(buf, 12));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) return rv;
+
+ location.swap(mLocation);
+ mLocationIsTemp = true;
+ }
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation);
+ if (NS_FAILED(rv)) return rv;
+
+ // we could wrap this output stream with a buffered output stream,
+ // but it shouldn't be necessary since we will be writing large
+ // chunks given to us via OnDataAvailable.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStopRequest(nsIRequest *request,
+ nsISupports *ctxt,
+ nsresult status)
+{
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ mObserver->OnDownloadComplete(this, request, ctxt, status, mLocation);
+ mObserver = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloader::ConsumeData(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsDownloader *self = (nsDownloader *) closure;
+ if (self->mSink)
+ return self->mSink->Write(fromRawSegment, count, writeCount);
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ uint32_t n;
+ return inStr->ReadSegments(ConsumeData, this, count, &n);
+}
diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h
new file mode 100644
index 000000000..b5c22393d
--- /dev/null
+++ b/netwerk/base/nsDownloader.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDownloader_h__
+#define nsDownloader_h__
+
+#include "nsIDownloader.h"
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsIOutputStream;
+
+class nsDownloader : public nsIDownloader
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsDownloader() : mLocationIsTemp(false) {}
+
+protected:
+ virtual ~nsDownloader();
+
+ static nsresult ConsumeData(nsIInputStream *in,
+ void *closure,
+ const char *fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount);
+
+ nsCOMPtr<nsIDownloadObserver> mObserver;
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIOutputStream> mSink;
+ bool mLocationIsTemp;
+};
+
+#endif // nsDownloader_h__
diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp
new file mode 100644
index 000000000..2ddb7ae98
--- /dev/null
+++ b/netwerk/base/nsFileStreams.cpp
@@ -0,0 +1,1153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#include "nsILocalFileWin.h"
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "private/pprio.h"
+
+#include "nsFileStreams.h"
+#include "nsIFile.h"
+#include "nsReadLine.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/FileUtils.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+
+#define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067
+
+typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType;
+
+using namespace mozilla::ipc;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStreamBase
+
+nsFileStreamBase::nsFileStreamBase()
+ : mFD(nullptr)
+ , mBehaviorFlags(0)
+ , mDeferredOpen(false)
+{
+}
+
+nsFileStreamBase::~nsFileStreamBase()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsFileStreamBase,
+ nsISeekableStream,
+ nsIFileMetadata)
+
+NS_IMETHODIMP
+nsFileStreamBase::Seek(int32_t whence, int64_t offset)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::Tell(int64_t *result)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::SetEOF()
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ // Some system calls require an EOF offset.
+ int64_t offset;
+ rv = Tell(&offset);
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(mFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // XXX not implemented
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetSize(int64_t* _retval)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ *_retval = int64_t(info.size);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetLastModified(int64_t* _retval)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ int64_t modTime = int64_t(info.modifyTime);
+ if (modTime == 0) {
+ *_retval = 0;
+ }
+ else {
+ *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval)
+{
+ nsresult rv = DoPendingOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *_retval = mFD;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Close()
+{
+ CleanUpOpen();
+
+ nsresult rv = NS_OK;
+ if (mFD) {
+ if (PR_Close(mFD) == PR_FAILURE)
+ rv = NS_BASE_STREAM_OSERROR;
+ mFD = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsFileStreamBase::Available(uint64_t* aResult)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // PR_Available with files over 4GB returns an error, so we have to
+ // use the 64-bit version of PR_Available.
+ int64_t avail = PR_Available64(mFD);
+ if (avail == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // If available is greater than 4GB, return 4GB
+ *aResult = (uint64_t)avail;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ nsresult rv = DoPendingOpen();
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is just a deferred file not found.
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFD) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
+ if (bytesRead == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ // ReadSegments is not implemented because it would be inefficient when
+ // the writer does not consume all data. If you want to call ReadSegments,
+ // wrap a BufferedInputStream around the file stream. That will call
+ // Read().
+
+ // If this is ever implemented you might need to modify
+ // nsPartialFileInputStream::ReadSegments
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFileStreamBase::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Flush(void)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Sync(mFD);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFD == nullptr)
+ return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Write(mFD, buf, count);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult
+nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult
+nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
+ int32_t aPerm, bool aDeferred)
+{
+ NS_ENSURE_STATE(aFile);
+
+ mOpenParams.ioFlags = aIoFlags;
+ mOpenParams.perm = aPerm;
+
+ if (aDeferred) {
+ // Clone the file, as it may change between now and the deferred open
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpenParams.localFile = do_QueryInterface(file);
+ NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
+
+ mDeferredOpen = true;
+ return NS_OK;
+ }
+
+ mOpenParams.localFile = aFile;
+
+ // Following call open() at main thread.
+ // Main thread might be blocked, while open a remote file.
+ return DoOpen();
+}
+
+void
+nsFileStreamBase::CleanUpOpen()
+{
+ mOpenParams.localFile = nullptr;
+ mDeferredOpen = false;
+}
+
+nsresult
+nsFileStreamBase::DoOpen()
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor!");
+ NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
+
+ PRFileDesc* fd;
+ nsresult rv;
+
+#ifdef XP_WIN
+ if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
+ nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
+ MOZ_ASSERT(file);
+
+ rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
+ mOpenParams.perm,
+ &fd);
+ } else
+#endif // XP_WIN
+ {
+ rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
+ mOpenParams.perm,
+ &fd);
+ }
+
+ CleanUpOpen();
+ if (NS_FAILED(rv))
+ return rv;
+ mFD = fd;
+
+ return NS_OK;
+}
+
+nsresult
+nsFileStreamBase::DoPendingOpen()
+{
+ if (!mDeferredOpen) {
+ return NS_OK;
+ }
+
+ return DoOpen();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_LOCALFILEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream,
+ nsIInputStream,
+ nsIFileInputStream,
+ nsISeekableStream,
+ nsILineInputStream)
+
+nsresult
+nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsFileInputStream* stream = new nsFileInputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+nsresult
+nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm)
+{
+ nsresult rv = NS_OK;
+
+ // If the previous file is open, close it
+ if (mFD) {
+ rv = Close();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Open the file
+ if (aIOFlags == -1)
+ aIOFlags = PR_RDONLY;
+ if (aPerm == -1)
+ aPerm = 0;
+
+ rv = MaybeOpen(aFile, aIOFlags, aPerm,
+ mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // if defer open is set, do not remove the file here.
+ // remove the file while Close() is called.
+ if ((mBehaviorFlags & DELETE_ON_CLOSE) &&
+ !(mBehaviorFlags & nsIFileInputStream::DEFER_OPEN)) {
+ // POSIX compatible filesystems allow a file to be unlinked while a
+ // file descriptor is still referencing the file. since we've already
+ // opened the file descriptor, we'll try to remove the file. if that
+ // fails, then we'll just remember the nsIFile and remove it after we
+ // close the file descriptor.
+ rv = aFile->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ // No need to remove it later. Clear the flag.
+ mBehaviorFlags &= ~DELETE_ON_CLOSE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags)
+{
+ NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = aBehaviorFlags;
+
+ mFile = aFile;
+ mIOFlags = aIOFlags;
+ mPerm = aPerm;
+
+ return Open(aFile, aIOFlags, aPerm);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Close()
+{
+ // Get the cache position at the time the file was close. This allows
+ // NS_SEEK_CUR on a closed file that has been opened with
+ // REOPEN_ON_REWIND.
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ // Get actual position. Not one modified by subclasses
+ nsFileStreamBase::Tell(&mCachedPosition);
+ }
+
+ // null out mLineBuffer in case Close() is called again after failing
+ mLineBuffer = nullptr;
+ nsresult rv = nsFileStreamBase::Close();
+ if (NS_FAILED(rv)) return rv;
+ if (mFile && (mBehaviorFlags & DELETE_ON_CLOSE)) {
+ rv = mFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to delete file");
+ // If we don't need to save the file for reopening, free it up
+ if (!(mBehaviorFlags & REOPEN_ON_REWIND)) {
+ mFile = nullptr;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is a deffered file not found.
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we're at the end of file and need to close
+ if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult)
+{
+ if (!mLineBuffer) {
+ mLineBuffer = new nsLineBuffer<char>;
+ }
+ return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ return SeekInternal(aWhence, aOffset);
+}
+
+nsresult
+nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf)
+{
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aClearBuf) {
+ mLineBuffer = nullptr;
+ }
+ if (!mFD) {
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ rv = Open(mFile, mIOFlags, mPerm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file was closed, and we do a relative seek, use the
+ // position we cached when we closed the file to seek to the right
+ // location.
+ if (aWhence == NS_SEEK_CUR) {
+ aWhence = NS_SEEK_SET;
+ aOffset += mCachedPosition;
+ }
+ } else {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ }
+
+ return nsFileStreamBase::Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Tell(int64_t *aResult)
+{
+ return nsFileStreamBase::Tell(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Available(uint64_t *aResult)
+{
+ return nsFileStreamBase::Available(aResult);
+}
+
+void
+nsFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ FileInputStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen()) && mFD) {
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd);
+ NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
+
+ params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
+
+ Close();
+ } else {
+ NS_WARNING("This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptorIndex() = UINT32_MAX;
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+ params.ioFlags() = mIOFlags;
+
+ aParams = params;
+}
+
+bool
+nsFileInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(!mDeferredOpen, "Deferring open?!");
+ NS_ASSERTION(!mFile, "Should never have a file here!");
+ NS_ASSERTION(!mPerm, "This should always be 0!");
+
+ if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
+
+ uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
+
+ FileDescriptor fd;
+ if (fileDescriptorIndex < aFileDescriptors.Length()) {
+ fd = aFileDescriptors[fileDescriptorIndex];
+ NS_WARNING_ASSERTION(fd.IsValid(),
+ "Received an invalid file descriptor!");
+ } else {
+ NS_WARNING("Received a bad file descriptor index!");
+ }
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ if (!XRE_IsParentProcess()) {
+ // A child process shouldn't close when it reads the end because it will
+ // not be able to reopen the file later.
+ mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
+
+ // A child process will not be able to reopen the file so this flag is
+ // meaningless.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+ }
+
+ mIOFlags = params.ioFlags();
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsFileInputStream::ExpectedSerializedLength()
+{
+ return Nothing();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPartialFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsPartialFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsPartialFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsPartialFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_PARTIALLOCALFILEINPUTSTREAM_CID)
+
+// Don't forward to nsFileInputStream as we don't want to QI to
+// nsIFileInputStream
+NS_INTERFACE_MAP_BEGIN(nsPartialFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIPartialFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsPartialFileInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsPartialFileInputStream,
+ nsIInputStream,
+ nsIPartialFileInputStream,
+ nsISeekableStream,
+ nsILineInputStream)
+
+nsresult
+nsPartialFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsPartialFileInputStream* stream = new nsPartialFileInputStream();
+
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Init(nsIFile* aFile, uint64_t aStart,
+ uint64_t aLength, int32_t aIOFlags,
+ int32_t aPerm, int32_t aBehaviorFlags)
+{
+ mStart = aStart;
+ mLength = aLength;
+ mPosition = 0;
+
+ nsresult rv = nsFileInputStream::Init(aFile, aIOFlags, aPerm,
+ aBehaviorFlags);
+
+ // aFile is a partial file, it must exist.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDeferredSeek = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Tell(int64_t *aResult)
+{
+ int64_t tell = 0;
+
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsFileInputStream::Tell(&tell);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ *aResult = tell - mStart;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Available(uint64_t* aResult)
+{
+ uint64_t available = 0;
+
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsFileInputStream::Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = TruncateSize(available);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t readsize = (uint32_t) TruncateSize(aCount);
+ if (readsize == 0 && mBehaviorFlags & CLOSE_ON_EOF) {
+ Close();
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ rv = nsFileInputStream::Read(aBuf, readsize, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPosition += readsize;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPartialFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ nsresult rv = DoPendingSeek();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t offset;
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ offset = mStart + aOffset;
+ break;
+ case NS_SEEK_CUR:
+ offset = mStart + mPosition + aOffset;
+ break;
+ case NS_SEEK_END:
+ offset = mStart + mLength + aOffset;
+ break;
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = nsFileInputStream::Seek(NS_SEEK_SET, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPosition = offset - mStart;
+ return rv;
+}
+
+void
+nsPartialFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ // Serialize the base class first.
+ InputStreamParams fileParams;
+ nsFileInputStream::Serialize(fileParams, aFileDescriptors);
+
+ PartialFileInputStreamParams params;
+
+ params.fileStreamParams() = fileParams.get_FileInputStreamParams();
+ params.begin() = mStart;
+ params.length() = mLength;
+
+ aParams = params;
+}
+
+bool
+nsPartialFileInputStream::Deserialize(
+ const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(!mStart, "Already have a start?!");
+ NS_ASSERTION(!mLength, "Already have a length?!");
+ NS_ASSERTION(!mPosition, "Already have a position?!");
+
+ if (aParams.type() != InputStreamParams::TPartialFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const PartialFileInputStreamParams& params =
+ aParams.get_PartialFileInputStreamParams();
+
+ // Deserialize the base class first.
+ InputStreamParams fileParams(params.fileStreamParams());
+ if (!nsFileInputStream::Deserialize(fileParams, aFileDescriptors)) {
+ NS_WARNING("Base class deserialize failed!");
+ return false;
+ }
+
+ NS_ASSERTION(mFD, "Must have a file descriptor now!");
+
+ mStart = params.begin();
+ mLength = params.length();
+ mPosition = 0;
+
+ if (!mStart) {
+ return true;
+ }
+
+ // XXX This is so broken. Main thread IO alert.
+ return NS_SUCCEEDED(nsFileInputStream::Seek(NS_SEEK_SET, mStart));
+}
+
+Maybe<uint64_t>
+nsPartialFileInputStream::ExpectedSerializedLength()
+{
+ return Some(mLength);
+}
+
+
+nsresult
+nsPartialFileInputStream::DoPendingSeek()
+{
+ if (!mDeferredSeek) {
+ return NS_OK;
+ }
+
+ mDeferredSeek = false;
+
+ // This is the first time to open the file, don't clear mLinebuffer.
+ // mLineBuffer might be already initialized by ReadLine().
+ return nsFileInputStream::SeekInternal(NS_SEEK_SET, mStart, false);
+}
+////////////////////////////////////////////////////////////////////////////////
+// nsFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream,
+ nsFileStreamBase,
+ nsIOutputStream,
+ nsIFileOutputStream)
+
+nsresult
+nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ nsFileOutputStream* stream = new nsFileOutputStream();
+ if (stream == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(stream);
+ nsresult rv = stream->QueryInterface(aIID, aResult);
+ NS_RELEASE(stream);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+
+ if (ioFlags == -1)
+ ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ if (perm <= 0)
+ perm = 0664;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Preallocate(int64_t aLength)
+{
+ if (!mFD) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mozilla::fallocate(mFD, aLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAtomicFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream,
+ nsFileOutputStream,
+ nsISafeOutputStream,
+ nsIOutputStream,
+ nsIFileOutputStream)
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
+ // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
+ // to existing file. So, throw an exception only if `PR_APPEND` is
+ // explicitly specified without `PR_TRUNCATE`.
+ if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
+}
+
+nsresult
+nsAtomicFileOutputStream::DoOpen()
+{
+ // Make sure mOpenParams.localFile will be empty if we bail somewhere in
+ // this function
+ nsCOMPtr<nsIFile> file;
+ file.swap(mOpenParams.localFile);
+
+ if (!file) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = file->Exists(&mTargetFileExists);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Can't tell if target file exists");
+ mTargetFileExists = true; // Safer to assume it exists - we just do more work.
+ }
+
+ // follow symlinks, for two reasons:
+ // 1) if a user has deliberately set up a profile file as a symlink, we honor it
+ // 2) to make the MoveToNative() in Finish() an atomic operation (which may not
+ // be the case if moving across directories on different filesystems).
+ nsCOMPtr<nsIFile> tempResult;
+ rv = file->Clone(getter_AddRefs(tempResult));
+ if (NS_SUCCEEDED(rv)) {
+ tempResult->SetFollowLinks(true);
+
+ // XP_UNIX ignores SetFollowLinks(), so we have to normalize.
+ if (mTargetFileExists) {
+ tempResult->Normalize();
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ uint32_t origPerm;
+ if (NS_FAILED(file->GetPermissions(&origPerm))) {
+ NS_ERROR("Can't get permissions of target file");
+ origPerm = mOpenParams.perm;
+ }
+ // XXX What if |perm| is more restrictive then |origPerm|?
+ // This leaves the user supplied permissions as they were.
+ rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // nsFileOutputStream::DoOpen will work on the temporary file, so we
+ // prepare it and place it in mOpenParams.localFile.
+ mOpenParams.localFile = tempResult;
+ mTempFile = tempResult;
+ mTargetFile = file;
+ rv = nsFileOutputStream::DoOpen();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Close()
+{
+ nsresult rv = nsFileOutputStream::Close();
+
+ // the consumer doesn't want the original file overwritten -
+ // so clean up by removing the temp file.
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Finish()
+{
+ nsresult rv = nsFileOutputStream::Close();
+
+ // if there is no temp file, don't try to move it over the original target.
+ // It would destroy the targetfile if close() is called twice.
+ if (!mTempFile)
+ return rv;
+
+ // Only overwrite if everything was ok, and the temp file could be closed.
+ if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
+ NS_ENSURE_STATE(mTargetFile);
+
+ if (!mTargetFileExists) {
+ // If the target file did not exist when we were initialized, then the
+ // temp file we gave out was actually a reference to the target file.
+ // since we succeeded in writing to the temp file (and hence succeeded
+ // in writing to the target file), there is nothing more to do.
+#ifdef DEBUG
+ bool equal;
+ if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal)
+ NS_WARNING("mTempFile not equal to mTargetFile");
+#endif
+ }
+ else {
+ nsAutoString targetFilename;
+ rv = mTargetFile->GetLeafName(targetFilename);
+ if (NS_SUCCEEDED(rv)) {
+ // This will replace target.
+ rv = mTempFile->MoveTo(nullptr, targetFilename);
+ if (NS_FAILED(rv))
+ mTempFile->Remove(false);
+ }
+ }
+ }
+ else {
+ mTempFile->Remove(false);
+
+ // if writing failed, propagate the failure code to the caller.
+ if (NS_FAILED(mWriteResult))
+ rv = mWriteResult;
+ }
+ mTempFile = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ nsresult rv = nsFileOutputStream::Write(buf, count, result);
+ if (NS_SUCCEEDED(mWriteResult)) {
+ if (NS_FAILED(rv))
+ mWriteResult = rv;
+ else if (count != *result)
+ mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+
+ if (NS_FAILED(mWriteResult) && count > 0)
+ NS_WARNING("writing to output stream failed! data may be lost");
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSafeFileOutputStream
+
+NS_IMETHODIMP
+nsSafeFileOutputStream::Finish()
+{
+ (void) Flush();
+ return nsAtomicFileOutputStream::Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileStream,
+ nsFileStreamBase,
+ nsIInputStream,
+ nsIOutputStream,
+ nsIFileStream)
+
+NS_IMETHODIMP
+nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+
+ if (ioFlags == -1)
+ ioFlags = PR_RDWR;
+ if (perm <= 0)
+ perm = 0;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileStream::DEFER_OPEN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h
new file mode 100644
index 000000000..22ef91770
--- /dev/null
+++ b/netwerk/base/nsFileStreams.h
@@ -0,0 +1,333 @@
+// /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileStreams_h__
+#define nsFileStreams_h__
+
+#include "nsAutoPtr.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsILineInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsReadLine.h"
+#include <algorithm>
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStreamBase : public nsISeekableStream,
+ public nsIFileMetadata
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIFILEMETADATA
+
+ nsFileStreamBase();
+
+protected:
+ virtual ~nsFileStreamBase();
+
+ nsresult Close();
+ nsresult Available(uint64_t* _retval);
+ nsresult Read(char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+ nsresult IsNonBlocking(bool* _retval);
+ nsresult Flush();
+ nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval);
+ nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+
+ PRFileDesc* mFD;
+
+ /**
+ * Flags describing our behavior. See the IDL file for possible values.
+ */
+ int32_t mBehaviorFlags;
+
+ /**
+ * Whether we have a pending open (see DEFER_OPEN in the IDL file).
+ */
+ bool mDeferredOpen;
+
+ struct OpenParams {
+ nsCOMPtr<nsIFile> localFile;
+ int32_t ioFlags;
+ int32_t perm;
+ };
+
+ /**
+ * Data we need to do an open.
+ */
+ OpenParams mOpenParams;
+
+ /**
+ * Prepares the data we need to open the file, and either does the open now
+ * by calling DoOpen(), or leaves it to be opened later by a call to
+ * DoPendingOpen().
+ */
+ nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm,
+ bool aDeferred);
+
+ /**
+ * Cleans up data prepared in MaybeOpen.
+ */
+ void CleanUpOpen();
+
+ /**
+ * Open the file. This is called either from MaybeOpen (during Init)
+ * or from DoPendingOpen (if DEFER_OPEN is used when initializing this
+ * stream). The default behavior of DoOpen is to open the file and save the
+ * file descriptor.
+ */
+ virtual nsresult DoOpen();
+
+ /**
+ * If there is a pending open, do it now. It's important for this to be
+ * inline since we do it in almost every stream API call.
+ */
+ inline nsresult DoPendingOpen();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileInputStream : public nsFileStreamBase,
+ public nsIFileInputStream,
+ public nsILineInputStream,
+ public nsIIPCSerializableInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEINPUTSTREAM
+ NS_DECL_NSILINEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Tell(int64_t *aResult) override;
+ NS_IMETHOD Available(uint64_t* _retval) override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+ uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount,
+ _retval);
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override
+ {
+ return nsFileStreamBase::IsNonBlocking(_retval);
+ }
+
+ // Overrided from nsFileStreamBase
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ nsFileInputStream()
+ : mLineBuffer(nullptr), mIOFlags(0), mPerm(0), mCachedPosition(0)
+ {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsFileInputStream()
+ {
+ Close();
+ }
+
+ nsresult SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf=true);
+
+ nsAutoPtr<nsLineBuffer<char> > mLineBuffer;
+
+ /**
+ * The file being opened.
+ */
+ nsCOMPtr<nsIFile> mFile;
+ /**
+ * The IO flags passed to Init() for the file open.
+ */
+ int32_t mIOFlags;
+ /**
+ * The permissions passed to Init() for the file open.
+ */
+ int32_t mPerm;
+
+ /**
+ * Cached position for Tell for automatically reopening streams.
+ */
+ int64_t mCachedPosition;
+
+protected:
+ /**
+ * Internal, called to open a file. Parameters are the same as their
+ * Init() analogues.
+ */
+ nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPartialFileInputStream : public nsFileInputStream,
+ public nsIPartialFileInputStream
+{
+public:
+ using nsFileInputStream::Init;
+ using nsFileInputStream::Read;
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPARTIALFILEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsPartialFileInputStream()
+ : mStart(0), mLength(0), mPosition(0), mDeferredSeek(false)
+ { }
+
+ NS_IMETHOD Tell(int64_t *aResult) override;
+ NS_IMETHOD Available(uint64_t *aResult) override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* aResult) override;
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsPartialFileInputStream()
+ { }
+
+ inline nsresult DoPendingSeek();
+
+private:
+ uint64_t TruncateSize(uint64_t aSize) {
+ return std::min<uint64_t>(mLength - mPosition, aSize);
+ }
+
+ uint64_t mStart;
+ uint64_t mLength;
+ uint64_t mPosition;
+ bool mDeferredSeek;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileOutputStream : public nsFileStreamBase,
+ public nsIFileOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEOUTPUTSTREAM
+ NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::)
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsFileOutputStream()
+ {
+ Close();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing is complete. This protects against incomplete writes
+ * due to the process or the thread being interrupted or crashed.
+ */
+class nsAtomicFileOutputStream : public nsFileOutputStream,
+ public nsISafeOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISAFEOUTPUTSTREAM
+
+ nsAtomicFileOutputStream() :
+ mTargetFileExists(true),
+ mWriteResult(NS_OK) {}
+
+ virtual nsresult DoOpen() override;
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Write(const char *buf, uint32_t count, uint32_t *result) override;
+ NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) override;
+
+protected:
+ virtual ~nsAtomicFileOutputStream()
+ {
+ Close();
+ }
+
+ nsCOMPtr<nsIFile> mTargetFile;
+ nsCOMPtr<nsIFile> mTempFile;
+
+ bool mTargetFileExists;
+ nsresult mWriteResult; // Internally set in Write()
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing + flushing is complete. This protects against more
+ * classes of software/hardware errors than nsAtomicFileOutputStream,
+ * at the expense of being more costly to the disk, OS and battery.
+ */
+class nsSafeFileOutputStream : public nsAtomicFileOutputStream
+{
+public:
+
+ NS_IMETHOD Finish() override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStream : public nsFileStreamBase,
+ public nsIInputStream,
+ public nsIOutputStream,
+ public nsIFileStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILESTREAM
+ NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::)
+
+ // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods
+ // Close() and IsNonBlocking()
+ NS_IMETHOD Flush() override
+ {
+ return nsFileStreamBase::Flush();
+ }
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::Write(aBuf, aCount, _retval);
+ }
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override
+ {
+ return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval);
+ }
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override
+ {
+ return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount,
+ _retval);
+ }
+
+protected:
+ virtual ~nsFileStream()
+ {
+ Close();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsFileStreams_h__
diff --git a/netwerk/base/nsIApplicationCache.idl b/netwerk/base/nsIApplicationCache.idl
new file mode 100644
index 000000000..9922feb59
--- /dev/null
+++ b/netwerk/base/nsIApplicationCache.idl
@@ -0,0 +1,205 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIFile;
+interface nsIURI;
+
+/**
+ * Application caches can store a set of namespace entries that affect
+ * loads from the application cache. If a load from the cache fails
+ * to match an exact cache entry, namespaces entries will be searched
+ * for a substring match, and should be applied appropriately.
+ */
+[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)]
+interface nsIApplicationCacheNamespace : nsISupports
+{
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. The "data" attribute is unused.
+ */
+ const unsigned long NAMESPACE_BYPASS = 1 << 0;
+
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. If the load fails, the cache entry
+ * specified by the "data" attribute should be loaded instead.
+ */
+ const unsigned long NAMESPACE_FALLBACK = 1 << 1;
+
+ /**
+ * Items matching this namespace should be cached
+ * opportunistically. Successful toplevel loads of documents
+ * in this namespace should be placed in the application cache.
+ * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify
+ * NAMESPACE_FALLBACK to supply a fallback entry.
+ */
+ const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2;
+
+ /**
+ * Initialize the namespace.
+ */
+ void init(in unsigned long itemType,
+ in ACString namespaceSpec,
+ in ACString data);
+
+ /**
+ * The namespace type.
+ */
+ readonly attribute unsigned long itemType;
+
+ /**
+ * The prefix of this namespace. This should be the asciiSpec of the
+ * URI prefix.
+ */
+ readonly attribute ACString namespaceSpec;
+
+ /**
+ * Data associated with this namespace, such as a fallback. URI data should
+ * use the asciiSpec of the URI.
+ */
+ readonly attribute ACString data;
+};
+
+/**
+ * Application caches store resources for offline use. Each
+ * application cache has a unique client ID for use with
+ * nsICacheService::openSession() to access the cache's entries.
+ *
+ * Each entry in the application cache can be marked with a set of
+ * types, as discussed in the WHAT-WG offline applications
+ * specification.
+ *
+ * All application caches with the same group ID belong to a cache
+ * group. Each group has one "active" cache that will service future
+ * loads. Inactive caches will be removed from the cache when they are
+ * no longer referenced.
+ */
+[scriptable, uuid(06568DAE-C374-4383-A122-0CC96C7177F2)]
+interface nsIApplicationCache : nsISupports
+{
+ /**
+ * Init this application cache instance to just hold the group ID and
+ * the client ID to work just as a handle to the real cache. Used on
+ * content process to simplify the application cache code.
+ */
+ void initAsHandle(in ACString groupId, in ACString clientId);
+
+ /**
+ * Entries in an application cache can be marked as one or more of
+ * the following types.
+ */
+
+ /* This item is the application manifest. */
+ const unsigned long ITEM_MANIFEST = 1 << 0;
+
+ /* This item was explicitly listed in the application manifest. */
+ const unsigned long ITEM_EXPLICIT = 1 << 1;
+
+ /* This item was navigated in a toplevel browsing context, and
+ * named this cache's group as its manifest. */
+ const unsigned long ITEM_IMPLICIT = 1 << 2;
+
+ /* This item was added by the dynamic scripting API */
+ const unsigned long ITEM_DYNAMIC = 1 << 3;
+
+ /* This item was listed in the application manifest, but named a
+ * different cache group as its manifest. */
+ const unsigned long ITEM_FOREIGN = 1 << 4;
+
+ /* This item was listed as a fallback entry. */
+ const unsigned long ITEM_FALLBACK = 1 << 5;
+
+ /* This item matched an opportunistic cache namespace and was
+ * cached accordingly. */
+ const unsigned long ITEM_OPPORTUNISTIC = 1 << 6;
+
+ /**
+ * URI of the manfiest specifying this application cache.
+ **/
+ readonly attribute nsIURI manifestURI;
+
+ /**
+ * The group ID for this cache group. It is an internally generated string
+ * and cannot be used as manifest URL spec.
+ **/
+ readonly attribute ACString groupID;
+
+ /**
+ * The client ID for this application cache. Clients can open a
+ * session with nsICacheService::createSession() using this client
+ * ID and a storage policy of STORE_OFFLINE to access this cache.
+ */
+ readonly attribute ACString clientID;
+
+ /**
+ * TRUE if the cache is the active cache for this group.
+ */
+ readonly attribute boolean active;
+
+ /**
+ * The disk usage of the application cache, in bytes.
+ */
+ readonly attribute unsigned long usage;
+
+ /**
+ * Makes this cache the active application cache for this group.
+ * Future loads associated with this group will come from this
+ * cache. Other caches from this cache group will be deactivated.
+ */
+ void activate();
+
+ /**
+ * Discard this application cache. Removes all cached resources
+ * for this cache. If this is the active application cache for the
+ * group, the group will be removed.
+ */
+ void discard();
+
+ /**
+ * Adds item types to a given entry.
+ */
+ void markEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Removes types from a given entry. If the resulting entry has
+ * no types left, the entry is removed.
+ */
+ void unmarkEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Gets the types for a given entry.
+ */
+ unsigned long getTypes(in ACString key);
+
+ /**
+ * Returns any entries in the application cache whose type matches
+ * one or more of the bits in typeBits.
+ */
+ void gatherEntries(in uint32_t typeBits,
+ out unsigned long count,
+ [array, size_is(count)] out string keys);
+
+ /**
+ * Add a set of namespace entries to the application cache.
+ * @param namespaces
+ * An nsIArray of nsIApplicationCacheNamespace entries.
+ */
+ void addNamespaces(in nsIArray namespaces);
+
+ /**
+ * Get the most specific namespace matching a given key.
+ */
+ nsIApplicationCacheNamespace getMatchingNamespace(in ACString key);
+
+ /**
+ * If set, this offline cache is placed in a different directory
+ * than the current application profile.
+ */
+ readonly attribute nsIFile profileDirectory;
+};
diff --git a/netwerk/base/nsIApplicationCacheChannel.idl b/netwerk/base/nsIApplicationCacheChannel.idl
new file mode 100644
index 000000000..410e2946d
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheChannel.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIApplicationCacheContainer.idl"
+
+/**
+ * Interface implemented by channels that support application caches.
+ */
+[scriptable, uuid(6FA816B1-6D5F-4380-9704-054D0908CFA3)]
+interface nsIApplicationCacheChannel : nsIApplicationCacheContainer
+{
+ /**
+ * TRUE when the resource came from the application cache. This
+ * might be false even there is assigned an application cache
+ * e.g. in case of fallback of load of an entry matching bypass
+ * namespace.
+ */
+ readonly attribute boolean loadedFromApplicationCache;
+
+ /**
+ * When true, the channel will ask its notification callbacks for
+ * an application cache if one is not explicitly provided. Default
+ * value is true.
+ *
+ * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen()
+ * is called.
+ */
+ attribute boolean inheritApplicationCache;
+
+ /**
+ * When true, the channel will choose an application cache if one
+ * was not explicitly provided and none is available from the
+ * notification callbacks. Default value is false.
+ *
+ * This attribute will not be transferred through a redirect.
+ *
+ * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen()
+ * is called.
+ */
+ attribute boolean chooseApplicationCache;
+
+ /**
+ * A shortcut method to mark the cache item of this channel as 'foreign'.
+ * See the 'cache selection algorithm' and CACHE_SELECTION_RELOAD
+ * action handling in nsContentSink.
+ */
+ void markOfflineCacheEntryAsForeign();
+
+ /**
+ * Set offline application cache object to instruct the channel
+ * to cache for offline use using this application cache.
+ */
+ attribute nsIApplicationCache applicationCacheForWrite;
+};
diff --git a/netwerk/base/nsIApplicationCacheContainer.idl b/netwerk/base/nsIApplicationCacheContainer.idl
new file mode 100644
index 000000000..af0b74cbe
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheContainer.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+
+/**
+ * Interface used by objects that can be associated with an
+ * application cache.
+ */
+[scriptable, uuid(bbb80700-1f7f-4258-aff4-1743cc5a7d23)]
+interface nsIApplicationCacheContainer : nsISupports
+{
+ attribute nsIApplicationCache applicationCache;
+};
diff --git a/netwerk/base/nsIApplicationCacheService.idl b/netwerk/base/nsIApplicationCacheService.idl
new file mode 100644
index 000000000..9b2b16955
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheService.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIURI;
+interface nsILoadContextInfo;
+
+/**
+ * The application cache service manages the set of application cache
+ * groups.
+ */
+[scriptable, uuid(b8b6546c-6cec-4bda-82df-08e006a97b56)]
+interface nsIApplicationCacheService : nsISupports
+{
+ /**
+ * Create group string identifying cache group according the manifest
+ * URL and the given principal.
+ */
+ ACString buildGroupIDForInfo(in nsIURI aManifestURL,
+ in nsILoadContextInfo aLoadContextInfo);
+ ACString buildGroupIDForSuffix(in nsIURI aManifestURL,
+ in ACString aOriginSuffix);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group.
+ */
+ nsIApplicationCache createApplicationCache(in ACString group);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group residing in a custom directory with a custom quota.
+ *
+ * @param group
+ * URL of the manifest
+ * @param directory
+ * Actually a reference to a profile directory where to
+ * create the OfflineCache sub-dir.
+ * @param quota
+ * Optional override of the default quota.
+ */
+ nsIApplicationCache createCustomApplicationCache(in ACString group,
+ in nsIFile profileDir,
+ in int32_t quota);
+
+ /**
+ * Get an application cache object for the given client ID.
+ */
+ nsIApplicationCache getApplicationCache(in ACString clientID);
+
+ /**
+ * Get the currently active cache object for a cache group.
+ */
+ nsIApplicationCache getActiveCache(in ACString group);
+
+ /**
+ * Deactivate the currently-active cache object for a cache group.
+ */
+ void deactivateGroup(in ACString group);
+
+ /**
+ * Evict offline cache entries, either all of them or those belonging
+ * to the given origin.
+ */
+ void evict(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Delete caches whom origin attributes matches the given pattern.
+ */
+ void evictMatchingOriginAttributes(in AString aPattern);
+
+ /**
+ * Try to find the best application cache to serve a resource.
+ */
+ nsIApplicationCache chooseApplicationCache(in ACString key,
+ [optional] in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Flags the key as being opportunistically cached.
+ *
+ * This method should also propagate the entry to other
+ * application caches with the same opportunistic namespace, but
+ * this is not currently implemented.
+ *
+ * @param cache
+ * The cache in which the entry is cached now.
+ * @param key
+ * The cache entry key.
+ */
+ void cacheOpportunistically(in nsIApplicationCache cache, in ACString key);
+
+ /**
+ * Get the list of application cache groups.
+ */
+ void getGroups([optional] out unsigned long count,
+ [array, size_is(count), retval] out string groupIDs);
+
+ /**
+ * Get the list of application cache groups in the order of
+ * activating time.
+ */
+ void getGroupsTimeOrdered([optional] out unsigned long count,
+ [array, size_is(count), retval] out string groupIDs);
+};
diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl
new file mode 100644
index 000000000..430f63b2e
--- /dev/null
+++ b/netwerk/base/nsIArrayBufferInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign an ArrayBuffer to the input stream.
+ *
+ * @param buffer - stream data
+ * @param byteOffset - stream data offset
+ * @param byteLen - stream data length
+ */
+ [implicit_jscontext]
+ void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl
new file mode 100644
index 000000000..633fe72b6
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+// You should prefer nsIAsyncStreamCopier2
+[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)]
+interface nsIAsyncStreamCopier : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aSourceBuffered
+ * true if aSource implements ReadSegments.
+ * @param aSinkBuffered
+ * true if aSink implements WriteSegments.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying.
+ * @param aCloseSink
+ * true if aSink should be closed after copying.
+ *
+ * NOTE: at least one of the streams must be buffered. If you do not know
+ * whether your streams are buffered, you should use nsIAsyncStreamCopier2
+ * instead.
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in boolean aSourceBuffered,
+ in boolean aSinkBuffered,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl
new file mode 100644
index 000000000..7de793f51
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier2.idl
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)]
+interface nsIAsyncStreamCopier2 : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * If neither the source nor the sink are buffered, buffering will
+ * be automatically added to the sink.
+ *
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying (this is generally
+ * the desired behavior).
+ * @param aCloseSink
+ * true if aSink should be closed after copying (this is generally
+ * the desired behavior).
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
new file mode 100644
index 000000000..8c81a142f
--- /dev/null
+++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)]
+interface nsIAsyncVerifyRedirectCallback : nsISupports
+{
+ /**
+ * Complement to nsIChannelEventSink asynchronous callback. The result of
+ * the redirect decision is passed through this callback.
+ *
+ * @param result
+ * Result of the redirect veto decision. If FAILED the redirect has been
+ * vetoed. If SUCCEEDED the redirect has been allowed by all consumers.
+ */
+ void onRedirectVerifyCallback(in nsresult result);
+};
diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl
new file mode 100644
index 000000000..484d59a8c
--- /dev/null
+++ b/netwerk/base/nsIAuthInformation.idl
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A object that hold authentication information. The caller of
+ * nsIAuthPrompt2::promptUsernameAndPassword or
+ * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this
+ * interface; the prompt implementation can then read the values here to prefill
+ * the dialog. After the user entered the authentication information, it should
+ * set the attributes of this object to indicate to the caller what was entered
+ * by the user.
+ */
+[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)]
+interface nsIAuthInformation : nsISupports
+{
+ /** @name Flags */
+ /* @{ */
+ /**
+ * This dialog belongs to a network host.
+ */
+ const uint32_t AUTH_HOST = 1;
+
+ /**
+ * This dialog belongs to a proxy.
+ */
+ const uint32_t AUTH_PROXY = 2;
+
+ /**
+ * This dialog needs domain information. The user interface should show a
+ * domain field, prefilled with the domain attribute's value.
+ */
+ const uint32_t NEED_DOMAIN = 4;
+
+ /**
+ * This dialog only asks for password information. Authentication prompts
+ * SHOULD NOT show a username field. Attempts to change the username field
+ * will have no effect. nsIAuthPrompt2 implementations should, however, show
+ * its initial value to the user in some form. For example, a paragraph in
+ * the dialog might say "Please enter your password for user jsmith at
+ * server intranet".
+ *
+ * This flag is mutually exclusive with #NEED_DOMAIN.
+ */
+ const uint32_t ONLY_PASSWORD = 8;
+
+ /**
+ * We have already tried to log in for this channel
+ * (with auth values from a previous promptAuth call),
+ * but it failed, so we now ask the user to provide a new, correct login.
+ *
+ * @see also RFC 2616, Section 10.4.2
+ */
+ const uint32_t PREVIOUS_FAILED = 16;
+
+ /**
+ * A cross-origin sub-resource requests an authentication.
+ * The message presented to users must reflect that.
+ */
+ const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32;
+ /* @} */
+
+ /**
+ * Flags describing this dialog. A bitwise OR of the flag values
+ * above.
+ *
+ * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set.
+ *
+ * Auth prompts should ignore flags they don't understand; especially, they
+ * should not throw an exception because of an unsupported flag.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * The server-supplied realm of the authentication as defined in RFC 2617.
+ * Can be the empty string if the protocol does not support realms.
+ * Otherwise, this is a human-readable string like "Secret files".
+ */
+ readonly attribute AString realm;
+
+ /**
+ * The authentication scheme used for this request, if applicable. If the
+ * protocol for this authentication does not support schemes, this will be
+ * the empty string. Otherwise, this will be a string such as "basic" or
+ * "digest". This string will always be in lowercase.
+ */
+ readonly attribute AUTF8String authenticationScheme;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the username entered by
+ * the user.
+ * This field can only be changed if the #ONLY_PASSWORD flag is not set.
+ */
+ attribute AString username;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * The password should not be shown in clear.
+ * On return, this parameter should contain the password entered by
+ * the user.
+ */
+ attribute AString password;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the domain entered by
+ * the user.
+ * This attribute is only used if flags include #NEED_DOMAIN.
+ */
+ attribute AString domain;
+};
+
diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl
new file mode 100644
index 000000000..8a446cb21
--- /dev/null
+++ b/netwerk/base/nsIAuthModule.idl
@@ -0,0 +1,145 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)]
+interface nsIAuthModule : nsISupports
+{
+ /**
+ * Default behavior.
+ */
+ const unsigned long REQ_DEFAULT = 0;
+
+ /**
+ * Client and server will be authenticated.
+ */
+ const unsigned long REQ_MUTUAL_AUTH = (1 << 0);
+
+ /**
+ * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH
+ * flag may also need to be specified in order for this flag to take
+ * effect.
+ */
+ const unsigned long REQ_DELEGATE = (1 << 1);
+
+ /**
+ * The authentication is required for a proxy connection.
+ */
+ const unsigned long REQ_PROXY_AUTH = (1 << 2);
+
+ /**
+ * Flags used for telemetry.
+ */
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0;
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1;
+ const unsigned long NTLM_MODULE_WIN_API_PROXY = 2;
+ const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3;
+ const unsigned long NTLM_MODULE_GENERIC_PROXY = 4;
+ const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5;
+ const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6;
+ const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7;
+
+ /** Other flags may be defined in the future */
+
+ /**
+ * Called to initialize an auth module. The other methods cannot be called
+ * unless this method succeeds.
+ *
+ * @param aServiceName
+ * the service name, which may be null if not applicable (e.g., for
+ * NTLM, this parameter should be null).
+ * @param aServiceFlags
+ * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT
+ * for default behavior).
+ * @param aDomain
+ * the authentication domain, which may be null if not applicable.
+ * @param aUsername
+ * the user's login name
+ * @param aPassword
+ * the user's password
+ */
+ void init(in string aServiceName,
+ in unsigned long aServiceFlags,
+ in wstring aDomain,
+ in wstring aUsername,
+ in wstring aPassword);
+
+ /**
+ * Called to get the next token in a sequence of authentication steps.
+ *
+ * @param aInToken
+ * A buffer containing the input token (e.g., a challenge from a
+ * server). This may be null.
+ * @param aInTokenLength
+ * The length of the input token.
+ * @param aOutToken
+ * If getNextToken succeeds, then aOutToken will point to a buffer
+ * to be sent in response to the server challenge. The length of
+ * this buffer is given by aOutTokenLength. The buffer at aOutToken
+ * must be recycled with a call to free.
+ * @param aOutTokenLength
+ * If getNextToken succeeds, then aOutTokenLength contains the
+ * length of the buffer (number of bytes) pointed to by aOutToken.
+ */
+ void getNextToken([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+ /**
+ * Once a security context has been established through calls to GetNextToken()
+ * it may be used to protect data exchanged between client and server. Calls
+ * to Wrap() are used to protect items of data to be sent to the server.
+ *
+ * @param aInToken
+ * A buffer containing the data to be sent to the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param confidential
+ * If set to true, Wrap() will encrypt the data, otherwise data will
+ * just be integrity protected (checksummed)
+ * @param aOutToken
+ * A buffer containing the resulting data to be sent to the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication
+ * mechanism does not support security layers.
+ */
+ void wrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ in boolean confidential,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+ /**
+ * Unwrap() is used to unpack, decrypt, and verify the checksums on data
+ * returned by a server when security layers are in use.
+ *
+ * @param aInToken
+ * A buffer containing the data received from the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param aOutToken
+ * A buffer containing the plaintext data from the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying
+ * authentication mechanism does not support security layers.
+ */
+ void unwrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+};
+
+%{C++
+/**
+ * nsIAuthModule implementations are registered under the following contract
+ * ID prefix:
+ */
+#define NS_AUTH_MODULE_CONTRACTID_PREFIX \
+ "@mozilla.org/network/auth-module;1?name="
+%}
diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl
new file mode 100644
index 000000000..bb9f88f60
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrompt;
+
+[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)]
+interface nsIAuthPrompt : nsISupports
+{
+ const uint32_t SAVE_PASSWORD_NEVER = 0;
+ const uint32_t SAVE_PASSWORD_FOR_SESSION = 1;
+ const uint32_t SAVE_PASSWORD_PERMANENTLY = 2;
+
+ /**
+ * Puts up a text input dialog with OK and Cancel buttons.
+ * Note: prompt uses separate args for the "in" and "out" values of the
+ * input field, whereas the other functions use a single inout arg.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param defaultText The default text to display in the text input box.
+ * @param result The value entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ in wstring defaultText,
+ out wstring result);
+
+ /**
+ * Puts up a username/password dialog with OK and Cancel buttons.
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+};
diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl
new file mode 100644
index 000000000..23c27c3d1
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt2.idl
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPromptCallback;
+interface nsIChannel;
+interface nsICancelable;
+interface nsIAuthInformation;
+
+/**
+ * An interface allowing to prompt for a username and password. This interface
+ * is usually acquired using getInterface on notification callbacks or similar.
+ * It can be used to prompt users for authentication information, either
+ * synchronously or asynchronously.
+ */
+[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)]
+interface nsIAuthPrompt2 : nsISupports
+{
+ /** @name Security Levels */
+ /* @{ */
+ /**
+ * The password will be sent unencrypted. No security provided.
+ */
+ const uint32_t LEVEL_NONE = 0;
+ /**
+ * Password will be sent encrypted, but the connection is otherwise
+ * insecure.
+ */
+ const uint32_t LEVEL_PW_ENCRYPTED = 1;
+ /**
+ * The connection, both for password and data, is secure.
+ */
+ const uint32_t LEVEL_SECURE = 2;
+ /* @} */
+
+ /**
+ * Requests a username and a password. Implementations will commonly show a
+ * dialog with a username and password field, depending on flags also a
+ * domain field.
+ *
+ * @param aChannel
+ * The channel that requires authentication.
+ * @param level
+ * One of the level constants from above. See there for descriptions
+ * of the levels.
+ * @param authInfo
+ * Authentication information object. The implementation should fill in
+ * this object with the information entered by the user before
+ * returning.
+ *
+ * @retval true
+ * Authentication can proceed using the values in the authInfo
+ * object.
+ * @retval false
+ * Authentication should be cancelled, usually because the user did
+ * not provide username/password.
+ *
+ * @note Exceptions thrown from this function will be treated like a
+ * return value of false.
+ */
+ boolean promptAuth(in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+
+ /**
+ * Asynchronously prompt the user for a username and password.
+ * This has largely the same semantics as promptUsernameAndPassword(),
+ * but must return immediately after calling and return the entered
+ * data in a callback.
+ *
+ * If the user closes the dialog using a cancel button or similar,
+ * the callback's nsIAuthPromptCallback::onAuthCancelled method must be
+ * called.
+ * Calling nsICancelable::cancel on the returned object SHOULD close the
+ * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided
+ * callback.
+ *
+ * This implementation may:
+ *
+ * 1) Coalesce identical prompts. This means prompts that are guaranteed to
+ * want the same auth information from the user. A single prompt will be
+ * shown; then the callbacks for all the coalesced prompts will be notified
+ * with the resulting auth information.
+ * 2) Serialize prompts that are all in the same "context" (this might mean
+ * application-wide, for a given window, or something else depending on
+ * the user interface) so that the user is not deluged with prompts.
+ *
+ * @throw
+ * This method may throw any exception when the prompt fails to queue e.g
+ * because of out-of-memory error. It must not throw when the prompt
+ * could already be potentially shown to the user. In that case information
+ * about the failure has to come through the callback. This way we
+ * prevent multiple dialogs shown to the user because consumer may fall
+ * back to synchronous prompt on synchronous failure of this method.
+ */
+ nsICancelable asyncPromptAuth(in nsIChannel aChannel,
+ in nsIAuthPromptCallback aCallback,
+ in nsISupports aContext,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+};
diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl
new file mode 100644
index 000000000..e763d4714
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPrompt;
+interface nsIAuthPrompt2;
+
+/**
+ * An interface for wrapping nsIAuthPrompt interfaces to make
+ * them usable via an nsIAuthPrompt2 interface.
+ */
+[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)]
+interface nsIAuthPromptAdapterFactory : nsISupports
+{
+ /**
+ * Wrap an object implementing nsIAuthPrompt so that it's usable via
+ * nsIAuthPrompt2.
+ */
+ nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt);
+};
diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl
new file mode 100644
index 000000000..bf9f2f2ac
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptCallback.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthInformation;
+
+/**
+ * Interface for callback methods for the asynchronous nsIAuthPrompt2 method.
+ * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync
+ * returns successfully. They MUST NOT call any method on this interface before
+ * promptPasswordAsync returns.
+ */
+[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)]
+interface nsIAuthPromptCallback : nsISupports
+{
+ /**
+ * Authentication information is available.
+ *
+ * @param aContext
+ * The context as passed to promptPasswordAsync
+ * @param aAuthInfo
+ * Authentication information. Must be the same object that was passed
+ * to promptPasswordAsync.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ void onAuthAvailable(in nsISupports aContext,
+ in nsIAuthInformation aAuthInfo);
+
+ /**
+ * Notification that the prompt was cancelled.
+ *
+ * @param aContext
+ * The context that was passed to promptPasswordAsync.
+ * @param userCancel
+ * If false, this prompt was cancelled by calling the
+ * the cancel method on the nsICancelable; otherwise,
+ * it was cancelled by the user.
+ */
+ void onAuthCancelled(in nsISupports aContext, in boolean userCancel);
+};
+
diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl
new file mode 100644
index 000000000..e8ff122ec
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptProvider.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)]
+interface nsIAuthPromptProvider : nsISupports
+{
+ /**
+ * Normal (non-proxy) prompt request.
+ */
+ const uint32_t PROMPT_NORMAL = 0;
+
+ /**
+ * Proxy auth request.
+ */
+ const uint32_t PROMPT_PROXY = 1;
+
+ /**
+ * Request a prompt interface for the given prompt reason;
+ * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or
+ * available for the given reason.
+ *
+ * @param aPromptReason The reason for the auth prompt;
+ * one of #PROMPT_NORMAL or #PROMPT_PROXY
+ * @param iid The desired interface, e.g.
+ * NS_GET_IID(nsIAuthPrompt2).
+ * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE
+ */
+ void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+};
diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl
new file mode 100644
index 000000000..df560d02f
--- /dev/null
+++ b/netwerk/base/nsIBackgroundFileSaver.idl
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIBackgroundFileSaverObserver;
+interface nsIFile;
+
+/**
+ * Allows saving data to a file, while handling all the input/output on a
+ * background thread, including the initial file name assignment and any
+ * subsequent renaming of the target file.
+ *
+ * This interface is designed for file downloads. Generally, they start in the
+ * temporary directory, while the user is selecting the final name. Then, they
+ * are moved to the chosen target directory with a ".part" extension appended to
+ * the file name. Finally, they are renamed when the download is completed.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIStreamListener
+ * allow data to be fed using an implementation of OnDataAvailable that never
+ * blocks the calling thread. They suspend the request that drives the stream
+ * listener in case too much data is being fed, and resume it when the data has
+ * been written. Calling OnStopRequest does not necessarily close the target
+ * file, and the Finish method must be called to complete the operation.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream
+ * allow data to be fed directly to the non-blocking output stream, that however
+ * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed.
+ * Closing the output stream does not necessarily close the target file, and the
+ * Finish method must be called to complete the operation.
+ *
+ * @remarks Implementations may require the consumer to always call Finish. If
+ * the object reference is released without calling Finish, a memory
+ * leak may occur, and the target file might be kept locked. All
+ * public methods of the interface may only be called from the main
+ * thread.
+ */
+[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)]
+interface nsIBackgroundFileSaver : nsISupports
+{
+ /**
+ * This observer receives notifications when the target file name changes and
+ * when the operation completes, successfully or not.
+ *
+ * @remarks A strong reference to the observer is held. Notification events
+ * are dispatched to the thread that created the object that
+ * implements nsIBackgroundFileSaver.
+ */
+ attribute nsIBackgroundFileSaverObserver observer;
+
+ /**
+ * An nsIArray of nsIX509CertList, representing a chain of X.509 signatures on
+ * the downloaded file. Each list may belong to a different signer and contain
+ * certificates all the way up to the root.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case this is called before the onSaveComplete method has been
+ * called to notify success, or enableSignatureInfo has not been
+ * called.
+ */
+ readonly attribute nsIArray signatureInfo;
+
+ /**
+ * The SHA-256 hash, in raw bytes, associated with the data that was saved.
+ *
+ * In case the enableAppend method has been called, the hash computation
+ * includes the contents of the existing file, if any.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case the enableSha256 method has not been called, or before the
+ * onSaveComplete method has been called to notify success.
+ */
+ readonly attribute ACString sha256Hash;
+
+ /**
+ * Instructs the component to compute the signatureInfo of the target file,
+ * and make it available in the signatureInfo property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSignatureInfo();
+
+ /**
+ * Instructs the component to compute the SHA-256 hash of the target file, and
+ * make it available in the sha256Hash property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSha256();
+
+ /**
+ * Instructs the component to append data to the initial target file, that
+ * will be specified by the first call to the setTarget method, instead of
+ * overwriting the file.
+ *
+ * If the initial target file does not exist, this method has no effect.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableAppend();
+
+ /**
+ * Sets the name of the output file to be written. The target can be changed
+ * after data has already been fed, in which case the existing file will be
+ * moved to the new destination.
+ *
+ * In case the specified file already exists, and this method is called for
+ * the first time, the file may be either overwritten or appended to, based on
+ * whether the enableAppend method was called. Subsequent calls always
+ * overwrite the specified target file with the previously saved data.
+ *
+ * No file will be written until this function is called at least once. It's
+ * recommended not to feed any data until the output file is set.
+ *
+ * If an input/output error occurs with the specified file, the save operation
+ * fails. Failure is notified asynchronously through the observer.
+ *
+ * @param aTarget
+ * New output file to be written.
+ * @param aKeepPartial
+ * Indicates whether aFile should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled. This is
+ * generally set for downloads that use temporary ".part" files.
+ */
+ void setTarget(in nsIFile aTarget, in bool aKeepPartial);
+
+ /**
+ * Terminates access to the output file, then notifies the observer with the
+ * specified status code. A failure code will force the operation to be
+ * canceled, in which case the output file will be deleted if requested.
+ *
+ * This forces the involved streams to be closed, thus no more data should be
+ * fed to the component after this method has been called.
+ *
+ * This is the last method that should be called on this object, and the
+ * target file name cannot be changed anymore after this method has been
+ * called. Conversely, before calling this method, the file can still be
+ * renamed even if all the data has been fed.
+ *
+ * @param aStatus
+ * Result code that determines whether the operation should succeed or
+ * be canceled, and is notified to the observer. If the operation
+ * fails meanwhile for other reasons, or the observer has been already
+ * notified of completion, this status code is ignored.
+ */
+ void finish(in nsresult aStatus);
+};
+
+[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)]
+interface nsIBackgroundFileSaverObserver : nsISupports
+{
+ /**
+ * Called when the name of the output file has been determined. This function
+ * may be called more than once if the target file is renamed while saving.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aTarget
+ * Name of the file that is being written.
+ */
+ void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget);
+
+ /**
+ * Called when the operation completed, and the target file has been closed.
+ * If the operation succeeded, the target file is ready to be used, otherwise
+ * it might have been already deleted.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aStatus
+ * Result code that determines whether the operation succeeded or
+ * failed, as well as the failure reason.
+ */
+ void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIBrowserSearchService.idl b/netwerk/base/nsIBrowserSearchService.idl
new file mode 100644
index 000000000..045973e0c
--- /dev/null
+++ b/netwerk/base/nsIBrowserSearchService.idl
@@ -0,0 +1,530 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+
+[scriptable, uuid(5799251f-5b55-4df7-a9e7-0c27812c469a)]
+interface nsISearchSubmission : nsISupports
+{
+ /**
+ * The POST data associated with a search submission, wrapped in a MIME
+ * input stream. May be null.
+ */
+ readonly attribute nsIInputStream postData;
+
+ /**
+ * The URI to submit a search to.
+ */
+ readonly attribute nsIURI uri;
+};
+
+[scriptable, uuid(620bd920-0491-48c8-99a8-d6047e64802d)]
+interface nsISearchEngine : nsISupports
+{
+ /**
+ * Gets a nsISearchSubmission object that contains information about what to
+ * send to the search engine, including the URI and postData, if applicable.
+ *
+ * @param data
+ * Data to add to the submission object.
+ * i.e. the search terms.
+ *
+ * @param responseType [optional]
+ * The MIME type that we'd like to receive in response
+ * to this submission. If null, will default to "text/html".
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @returns A nsISearchSubmission object that contains information about what
+ * to send to the search engine. If no submission can be
+ * obtained for the given responseType, returns null.
+ */
+ nsISearchSubmission getSubmission(in AString data,
+ [optional] in AString responseType,
+ [optional] in AString purpose);
+
+ /**
+ * Adds a parameter to the search engine's submission data. This should only
+ * be called on engines created via addEngineWithDetails.
+ *
+ * @param name
+ * The parameter's name. Must not be null.
+ *
+ * @param value
+ * The value to pass. If value is "{searchTerms}", it will be
+ * substituted with the user-entered data when retrieving the
+ * submission. Must not be null.
+ *
+ * @param responseType
+ * Since an engine can have several different request URLs,
+ * differentiated by response types, this parameter selects
+ * a request to add parameters to. If null, will default
+ * to "text/html"
+ *
+ * @throws NS_ERROR_FAILURE if the search engine is read-only.
+ * @throws NS_ERROR_INVALID_ARG if name or value are null.
+ */
+ void addParam(in AString name, in AString value, in AString responseType);
+
+ /**
+ * Determines whether the engine can return responses in the given
+ * MIME type. Returns true if the engine spec has a URL with the
+ * given responseType, false otherwise.
+ *
+ * @param responseType
+ * The MIME type to check for
+ */
+ boolean supportsResponseType(in AString responseType);
+
+ /**
+ * Returns a string with the URL to an engine's icon matching both width and
+ * height. Returns null if icon with specified dimensions is not found.
+ *
+ * @param width
+ * Width of the requested icon.
+ * @param height
+ * Height of the requested icon.
+ */
+ AString getIconURLBySize(in long width, in long height);
+
+ /**
+ * Gets an array of all available icons. Each entry is an object with
+ * width, height and url properties. width and height are numeric and
+ * represent the icon's dimensions. url is a string with the URL for
+ * the icon.
+ */
+ jsval getIcons();
+
+ /**
+ * Opens a speculative connection to the engine's search URI
+ * (and suggest URI, if different) to reduce request latency
+ *
+ * @param options
+ * An object that must contain the following fields:
+ * {window} the content window for the window performing the search
+ *
+ * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required
+ * elemeents
+ */
+ void speculativeConnect(in jsval options);
+
+ /**
+ * An optional shortcut alias for the engine.
+ * When non-null, this is a unique identifier.
+ */
+ attribute AString alias;
+
+ /**
+ * A text description describing the engine.
+ */
+ readonly attribute AString description;
+
+ /**
+ * Whether the engine should be hidden from the user.
+ */
+ attribute boolean hidden;
+
+ /**
+ * A nsIURI corresponding to the engine's icon, stored locally. May be null.
+ */
+ readonly attribute nsIURI iconURI;
+
+ /**
+ * The display name of the search engine. This is a unique identifier.
+ */
+ readonly attribute AString name;
+
+ /**
+ * A URL string pointing to the engine's search form.
+ */
+ readonly attribute AString searchForm;
+
+ /**
+ * An optional unique identifier for this search engine within the context of
+ * the distribution, as provided by the distributing entity.
+ */
+ readonly attribute AString identifier;
+
+ /**
+ * Gets a string representing the hostname from which search results for a
+ * given responseType are returned, minus the leading "www." (if present).
+ * This can be specified as an url attribute in the engine description file,
+ * but will default to host from the <Url>'s template otherwise.
+ *
+ * @param responseType [optional]
+ * The MIME type to get resultDomain for. Defaults to "text/html".
+ *
+ * @return the resultDomain for the given responseType.
+ */
+ AString getResultDomain([optional] in AString responseType);
+};
+
+[scriptable, uuid(0dc93e51-a7bf-4a16-862d-4b3469ff6206)]
+interface nsISearchParseSubmissionResult : nsISupports
+{
+ /**
+ * The search engine associated with the URL passed in to
+ * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent
+ * a search submission.
+ */
+ readonly attribute nsISearchEngine engine;
+
+ /**
+ * String containing the sought terms. This can be an empty string in case no
+ * terms were specified or the URL does not represent a search submission.
+ */
+ readonly attribute AString terms;
+
+ /**
+ * The offset of the string |terms| in the URL passed in to
+ * nsISearchEngine::parseSubmissionURL, or -1 if the URL does not represent
+ * a search submission.
+ */
+ readonly attribute long termsOffset;
+
+ /**
+ * The length of the |terms| in the original encoding of the URL passed in to
+ * nsISearchEngine::parseSubmissionURL. If the search term in the original
+ * URL is encoded then this will be bigger than |terms.length|.
+ */
+ readonly attribute long termsLength;
+};
+
+[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)]
+interface nsISearchInstallCallback : nsISupports
+{
+ const unsigned long ERROR_UNKNOWN_FAILURE = 0x1;
+ const unsigned long ERROR_DUPLICATE_ENGINE = 0x2;
+
+ /**
+ * Called to indicate that the engine addition process succeeded.
+ *
+ * @param engine
+ * The nsISearchEngine object that was added (will not be null).
+ */
+ void onSuccess(in nsISearchEngine engine);
+
+ /**
+ * Called to indicate that the engine addition process failed.
+ *
+ * @param errorCode
+ * One of the ERROR_* values described above indicating the cause of
+ * the failure.
+ */
+ void onError(in unsigned long errorCode);
+};
+
+/**
+ * Callback for asynchronous initialization of nsIBrowserSearchService
+ */
+[scriptable, function, uuid(02256156-16e4-47f1-9979-76ff98ceb590)]
+interface nsIBrowserSearchInitObserver : nsISupports
+{
+ /**
+ * Called once initialization of the browser search service is complete.
+ *
+ * @param aStatus The status of that service.
+ */
+ void onInitComplete(in nsresult aStatus);
+};
+
+[scriptable, uuid(150ef720-bbe2-4169-b9f3-ef7ec0654ced)]
+interface nsIBrowserSearchService : nsISupports
+{
+ /**
+ * Start asynchronous initialization.
+ *
+ * The callback is triggered once initialization is complete, which may be
+ * immediately, if initialization has already been completed by some previous
+ * call to this method. The callback is always invoked asynchronously.
+ *
+ * @param aObserver An optional object observing the end of initialization.
+ */
+ void init([optional] in nsIBrowserSearchInitObserver aObserver);
+
+ /**
+ * Determine whether initialization has been completed.
+ *
+ * Clients of the service can use this attribute to quickly determine whether
+ * initialization is complete, and decide to trigger some immediate treatment,
+ * to launch asynchronous initialization or to bailout.
+ *
+ * Note that this attribute does not indicate that initialization has succeeded.
+ *
+ * @return |true| if the search service is now initialized, |false| if
+ * initialization has not been triggered yet.
+ */
+ readonly attribute bool isInitialized;
+
+ /**
+ * Resets the default engine to its original value.
+ */
+ void resetToOriginalDefaultEngine();
+
+ /**
+ * Checks if an EngineURL of type URLTYPE_SEARCH_HTML exists for
+ * any engine, with a matching method, template URL, and query params.
+ *
+ * @param method
+ * The HTTP request method used when submitting a search query.
+ * Must be a case insensitive value of either "get" or "post".
+ *
+ * @param url
+ * The URL to which search queries should be sent.
+ * Must not be null.
+ *
+ * @param formData
+ * The un-sorted form data used as query params.
+ */
+ boolean hasEngineWithURL(in AString method, in AString url, in jsval formData);
+
+ /**
+ * Adds a new search engine from the file at the supplied URI, optionally
+ * asking the user for confirmation first. If a confirmation dialog is
+ * shown, it will offer the option to begin using the newly added engine
+ * right away.
+ *
+ * @param engineURL
+ * The URL to the search engine's description file.
+ *
+ * @param dataType
+ * Obsolete, the value is ignored.
+ *
+ * @param iconURL
+ * A URL string to an icon file to be used as the search engine's
+ * icon. This value may be overridden by an icon specified in the
+ * engine description file.
+ *
+ * @param confirm
+ * A boolean value indicating whether the user should be asked for
+ * confirmation before this engine is added to the list. If this
+ * value is false, the engine will be added to the list upon successful
+ * load, but it will not be selected as the current engine.
+ *
+ * @param callback
+ * A nsISearchInstallCallback that will be notified when the
+ * addition is complete, or if the addition fails. It will not be
+ * called if addEngine throws an exception.
+ *
+ * @throws NS_ERROR_FAILURE if the description file cannot be successfully
+ * loaded.
+ */
+ void addEngine(in AString engineURL, in long dataType, in AString iconURL,
+ in boolean confirm, [optional] in nsISearchInstallCallback callback);
+
+ /**
+ * Adds a new search engine, without asking the user for confirmation and
+ * without starting to use it right away.
+ *
+ * @param name
+ * The search engine's name. Must be unique. Must not be null.
+ *
+ * @param iconURL
+ * Optional: A URL string pointing to the icon to be used to represent
+ * the engine.
+ *
+ * @param alias
+ * Optional: A unique shortcut that can be used to retrieve the
+ * search engine.
+ *
+ * @param description
+ * Optional: a description of the search engine.
+ *
+ * @param method
+ * The HTTP request method used when submitting a search query.
+ * Must be a case insensitive value of either "get" or "post".
+ *
+ * @param url
+ * The URL to which search queries should be sent.
+ * Must not be null.
+ *
+ * @param extensionID [optional]
+ * Optional: The correct extensionID if called by an add-on.
+ */
+ void addEngineWithDetails(in AString name,
+ in AString iconURL,
+ in AString alias,
+ in AString description,
+ in AString method,
+ in AString url,
+ [optional] in AString extensionID);
+
+ /**
+ * Un-hides all engines installed in the directory corresponding to
+ * the directory service's NS_APP_SEARCH_DIR key. (i.e. the set of
+ * engines returned by getDefaultEngines)
+ */
+ void restoreDefaultEngines();
+
+ /**
+ * Returns an engine with the specified alias.
+ *
+ * @param alias
+ * The search engine's alias.
+ * @returns The corresponding nsISearchEngine object, or null if it doesn't
+ * exist.
+ */
+ nsISearchEngine getEngineByAlias(in AString alias);
+
+ /**
+ * Returns an engine with the specified name.
+ *
+ * @param aEngineName
+ * The name of the engine.
+ * @returns The corresponding nsISearchEngine object, or null if it doesn't
+ * exist.
+ */
+ nsISearchEngine getEngineByName(in AString aEngineName);
+
+ /**
+ * Returns an array of all installed search engines.
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Returns an array of all installed search engines whose hidden attribute is
+ * false.
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getVisibleEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Returns an array of all default search engines. This includes all loaded
+ * engines that aren't in the user's profile directory
+ * (NS_APP_USER_SEARCH_DIR).
+ *
+ * @returns an array of nsISearchEngine objects.
+ */
+ void getDefaultEngines(
+ [optional] out unsigned long engineCount,
+ [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+
+ /**
+ * Moves a visible search engine.
+ *
+ * @param engine
+ * The engine to move.
+ * @param newIndex
+ * The engine's new index in the set of visible engines.
+ *
+ * @throws NS_ERROR_FAILURE if newIndex is out of bounds, or if engine is
+ * hidden.
+ */
+ void moveEngine(in nsISearchEngine engine, in long newIndex);
+
+ /**
+ * Removes the search engine. If the search engine is installed in a global
+ * location, this will just hide the engine. If the engine is in the user's
+ * profile directory, it will be removed from disk.
+ *
+ * @param engine
+ * The engine to remove.
+ */
+ void removeEngine(in nsISearchEngine engine);
+
+ /**
+ * The original Engine object that is the default for this region,
+ * ignoring changes the user may have subsequently made.
+ */
+ readonly attribute nsISearchEngine originalDefaultEngine;
+
+ /**
+ * Alias for the currentEngine attribute, kept for add-on compatibility.
+ */
+ attribute nsISearchEngine defaultEngine;
+
+ /**
+ * The currently active search engine.
+ * Unless the application doesn't ship any search plugin, this should never
+ * be null. If the currently active engine is removed, this attribute will
+ * fallback first to the original default engine if it's not hidden, then to
+ * the first visible engine, and as a last resort it will unhide the original
+ * default engine.
+ */
+ attribute nsISearchEngine currentEngine;
+
+ /**
+ * Gets a representation of the default engine in an anonymized JSON
+ * string suitable for recording in the Telemetry environment.
+ *
+ * @return an object containing anonymized info about the default engine:
+ * name, loadPath, submissionURL (for default engines).
+ */
+ jsval getDefaultEngineInfo();
+
+ /**
+ * Determines if the provided URL represents results from a search engine, and
+ * provides details about the match.
+ *
+ * The lookup mechanism checks whether the domain name and path of the
+ * provided HTTP or HTTPS URL matches one of the known values for the visible
+ * search engines. The match does not depend on which of the schemes is used.
+ * The expected URI parameter for the search terms must exist in the query
+ * string, but other parameters are ignored.
+ *
+ * @param url
+ * String containing the URL to parse, for example
+ * "https://www.google.com/search?q=terms".
+ */
+ nsISearchParseSubmissionResult parseSubmissionURL(in AString url);
+};
+
+%{ C++
+/**
+ * The observer topic to listen to for actions performed on installed
+ * search engines.
+ */
+#define SEARCH_ENGINE_TOPIC "browser-search-engine-modified"
+
+/**
+ * Sent when an engine is removed from the data store.
+ */
+#define SEARCH_ENGINE_REMOVED "engine-removed"
+
+/**
+ * Sent when an engine is changed. This includes when the engine's "hidden"
+ * property is changed.
+ */
+#define SEARCH_ENGINE_CHANGED "engine-changed"
+
+/**
+ * Sent when an engine is added to the list of available engines.
+ */
+#define SEARCH_ENGINE_ADDED "engine-added"
+
+/**
+ * Sent when a search engine being installed from a remote plugin description
+ * file is completely loaded. This is used internally by the search service as
+ * an indication of when the engine can be added to the internal store, and
+ * therefore should not be used to detect engine availability. It is always
+ * followed by an "added" notification.
+ */
+#define SEARCH_ENGINE_LOADED "engine-loaded"
+
+/**
+ * Sent when the "current" engine is changed.
+ */
+#define SEARCH_ENGINE_CURRENT "engine-current";
+
+/**
+ * Sent when the "default" engine is changed.
+ */
+#define SEARCH_ENGINE_DEFAULT "engine-default";
+
+
+
+%}
diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl
new file mode 100644
index 000000000..60b9768b9
--- /dev/null
+++ b/netwerk/base/nsIBufferedStreams.idl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+/**
+ * An input stream that reads ahead and keeps a buffer coming from another input
+ * stream so that fewer accesses to the underlying stream are necessary.
+ */
+[scriptable, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedInputStream : nsIInputStream
+{
+ /**
+ * @param fillFromStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIInputStream fillFromStream,
+ in unsigned long bufferSize);
+};
+
+/**
+ * An output stream that stores up data to write out to another output stream
+ * and does the entire write only when the buffer is full, so that fewer writes
+ * to the underlying output stream are necessary.
+ */
+[scriptable, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedOutputStream : nsIOutputStream
+{
+ /**
+ * @param sinkToStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIOutputStream sinkToStream,
+ in unsigned long bufferSize);
+};
diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl
new file mode 100644
index 000000000..b558016a5
--- /dev/null
+++ b/netwerk/base/nsIByteRangeRequest.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)]
+interface nsIByteRangeRequest : nsISupports
+{
+ /**
+ * Returns true IFF this request is a byte range request, otherwise it
+ * returns false (This is effectively the same as checking to see if
+ * |startRequest| is zero and |endRange| is the content length.)
+ */
+ readonly attribute boolean isByteRangeRequest;
+
+ /**
+ * Absolute start position in remote file for this request.
+ */
+ readonly attribute long long startRange;
+
+ /**
+ * Absolute end postion in remote file for this request
+ */
+ readonly attribute long long endRange;
+};
diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl
new file mode 100644
index 000000000..f6d3c7b73
--- /dev/null
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIOutputStream;
+
+[scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
+interface nsICacheInfoChannel : nsISupports
+{
+ /**
+ * Get expiration time from cache token. This attribute is equivalent to
+ * nsICachingChannel.cacheToken.expirationTime.
+ */
+ readonly attribute uint32_t cacheTokenExpirationTime;
+
+ /**
+ * Set/get charset of cache entry. Accessing this attribute is equivalent to
+ * calling nsICachingChannel.cacheToken.getMetaDataElement("charset") and
+ * nsICachingChannel.cacheToken.setMetaDataElement("charset").
+ */
+ attribute ACString cacheTokenCachedCharset;
+
+ /**
+ * TRUE if this channel's data is being loaded from the cache. This value
+ * is undefined before the channel fires its OnStartRequest notification
+ * and after the channel fires its OnStopRequest notification.
+ */
+ boolean isFromCache();
+
+ /**
+ * Set/get the cache key... uniquely identifies the data in the cache
+ * for this channel. Holding a reference to this key does NOT prevent
+ * the cached data from being removed.
+ *
+ * A cache key retrieved from a particular instance of nsICacheInfoChannel
+ * could be set on another instance of nsICacheInfoChannel provided the
+ * underlying implementations are compatible and provided the new
+ * channel instance was created with the same URI. The implementation of
+ * nsICacheInfoChannel would be expected to use the cache entry identified
+ * by the cache token. Depending on the value of nsIRequest::loadFlags,
+ * the cache entry may be validated, overwritten, or simply read.
+ *
+ * The cache key may be NULL indicating that the URI of the channel is
+ * sufficient to locate the same cache entry. Setting a NULL cache key
+ * is likewise valid.
+ */
+ attribute nsISupports cacheKey;
+
+ /**
+ * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set,
+ * but without affecting the loads for the entire loadGroup in case of this
+ * channel being the default load group's channel.
+ */
+ attribute boolean allowStaleCacheContent;
+
+ /**
+ * Calling this method instructs the channel to serve the alternative data
+ * if that was previously saved in the cache, otherwise it will serve the
+ * real data.
+ * Must be called before AsyncOpen.
+ */
+ void preferAlternativeDataType(in ACString type);
+
+ /**
+ * Holds the type of the alternative data representation that the channel
+ * is returning.
+ * Is empty string if no alternative data representation was requested, or
+ * if the requested representation wasn't found in the cache.
+ * Can only be called during or after OnStartRequest.
+ */
+ readonly attribute ACString alternativeDataType;
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ * Must be called after the OnStopRequest that delivered the real data.
+ * The consumer may choose to replace the saved alt representation.
+ * Opening the output stream will fail if there are any open input streams
+ * reading the already saved alt representation.
+ */
+ nsIOutputStream openAlternativeOutputStream(in ACString type);
+};
diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl
new file mode 100644
index 000000000..63f65b1a4
--- /dev/null
+++ b/netwerk/base/nsICachingChannel.idl
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICacheInfoChannel.idl"
+
+interface nsIFile;
+
+/**
+ * A channel may optionally implement this interface to allow clients
+ * to affect its behavior with respect to how it uses the cache service.
+ *
+ * This interface provides:
+ * 1) Support for "stream as file" semantics (for JAR and plugins).
+ * 2) Support for "pinning" cached data in the cache (for printing and save-as).
+ * 3) Support for uniquely identifying cached data in cases when the URL
+ * is insufficient (e.g., HTTP form submission).
+ */
+[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
+interface nsICachingChannel : nsICacheInfoChannel
+{
+ /**
+ * Set/get the cache token... uniquely identifies the data in the cache.
+ * Holding a reference to this token prevents the cached data from being
+ * removed.
+ *
+ * A cache token retrieved from a particular instance of nsICachingChannel
+ * could be set on another instance of nsICachingChannel provided the
+ * underlying implementations are compatible. The implementation of
+ * nsICachingChannel would be expected to only read from the cache entry
+ * identified by the cache token and not try to validate it.
+ *
+ * The cache token can be QI'd to a nsICacheEntryInfo if more detail
+ * about the cache entry is needed (e.g., expiration time).
+ */
+ attribute nsISupports cacheToken;
+
+ /**
+ * The same as above but accessing the offline app cache token if there
+ * is any.
+ *
+ * @throws
+ * NS_ERROR_NOT_AVAILABLE when there is not offline cache token
+ */
+ attribute nsISupports offlineCacheToken;
+
+ /**
+ * Instructs the channel to only store the metadata of the entry, and not
+ * the content. When reading an existing entry, this automatically sets
+ * LOAD_ONLY_IF_MODIFIED flag.
+ * Must be called before asyncOpen().
+ */
+ attribute boolean cacheOnlyMetadata;
+
+ /**
+ * Tells the channel to use the pinning storage.
+ */
+ attribute boolean pin;
+
+ /**
+ * Overrides cache validation for a time specified in seconds.
+ *
+ * @param aSecondsToTheFuture
+ *
+ */
+ void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**************************************************************************
+ * Caching channel specific load flags:
+ */
+
+ /**
+ * This load flag inhibits fetching from the net. An error of
+ * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
+ * onStopRequest if network IO is necessary to complete the request.
+ *
+ * This flag can be used to find out whether fetching this URL would
+ * cause validation of the cache entry via the network.
+ *
+ * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all
+ * loads to fail. This flag differs from LOAD_ONLY_FROM_CACHE in that
+ * this flag fails the load if validation is required while
+ * LOAD_ONLY_FROM_CACHE skips validation where possible.
+ */
+ const unsigned long LOAD_NO_NETWORK_IO = 1 << 26;
+
+ /**
+ * This load flag causes the offline cache to be checked when fetching
+ * a request. It will be set automatically if the browser is offline.
+ *
+ * This flag will not be transferred through a redirect.
+ */
+ const unsigned long LOAD_CHECK_OFFLINE_CACHE = 1 << 27;
+
+ /**
+ * This load flag causes the local cache to be skipped when fetching a
+ * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load
+ * (i.e., it does not affect proxy caches).
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28;
+
+ /**
+ * This load flag causes the local cache to be skipped if the request
+ * would otherwise block waiting to access the cache.
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29;
+
+ /**
+ * This load flag inhibits fetching from the net if the data in the cache
+ * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent
+ * to the listener's onStopRequest in this case. This flag is set
+ * automatically when the application is offline.
+ */
+ const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30;
+
+ /**
+ * This load flag controls what happens when a document would be loaded
+ * from the cache to satisfy a call to AsyncOpen. If this attribute is
+ * set to TRUE, then the document will not be loaded from the cache. A
+ * stream listener can check nsICachingChannel::isFromCache to determine
+ * if the AsyncOpen will actually result in data being streamed.
+ *
+ * If this flag has been set, and the request can be satisfied via the
+ * cache, then the OnDataAvailable events will be skipped. The listener
+ * will only see OnStartRequest followed by OnStopRequest.
+ */
+ const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31;
+};
diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl
new file mode 100644
index 000000000..c558dc6e2
--- /dev/null
+++ b/netwerk/base/nsICancelable.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides a means to cancel an operation that is in progress.
+ */
+[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)]
+interface nsICancelable : nsISupports
+{
+ /**
+ * Call this method to request that this object abort whatever operation it
+ * may be performing.
+ *
+ * @param aReason
+ * Pass a failure code to indicate the reason why this operation is
+ * being canceled. It is an error to pass a success code.
+ */
+ void cancel(in nsresult aReason);
+};
diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl
new file mode 100644
index 000000000..94d9d6e9a
--- /dev/null
+++ b/netwerk/base/nsICaptivePortalService.idl
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)]
+interface nsICaptivePortalServiceCallback : nsISupports
+{
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success, in nsresult error);
+};
+
+/**
+ * Service used for captive portal detection.
+ * The service is only active in the main process. It is also available in the
+ * content process, but only to mirror the captive portal state from the main
+ * process.
+ */
+[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)]
+interface nsICaptivePortalService : nsISupports
+{
+ const long UNKNOWN = 0;
+ const long NOT_CAPTIVE = 1;
+ const long UNLOCKED_PORTAL = 2;
+ const long LOCKED_PORTAL = 3;
+
+ /**
+ * Called from XPCOM to trigger a captive portal recheck.
+ * A network request will only be performed if no other checks are currently
+ * ongoing.
+ * Will not do anything if called in the content process.
+ */
+ void recheckCaptivePortal();
+
+ /**
+ * Returns the state of the captive portal.
+ */
+ readonly attribute long state;
+
+ /**
+ * Returns the time difference between NOW and the last time a request was
+ * completed in milliseconds.
+ */
+ readonly attribute unsigned long long lastChecked;
+};
+
+%{C++
+/**
+ * This observer notification will be emitted when the captive portal state
+ * changes. After receiving it, the ContentParent will send an IPC message
+ * to the ContentChild, which will set the state in the captive portal service
+ * in the child.
+ */
+#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state"
+
+%}
diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl
new file mode 100644
index 000000000..743e94292
--- /dev/null
+++ b/netwerk/base/nsIChannel.idl
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+#include "nsILoadInfo.idl"
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsIInputStream;
+interface nsIStreamListener;
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+/**
+ * The nsIChannel interface allows clients to construct "GET" requests for
+ * specific protocols, and manage them in a uniform way. Once a channel is
+ * created (via nsIIOService::newChannel), parameters for that request may
+ * be set by using the channel attributes, or by QI'ing to a subclass of
+ * nsIChannel for protocol-specific parameters. Then, the URI can be fetched
+ * by calling nsIChannel::open or nsIChannel::asyncOpen.
+ *
+ * After a request has been completed, the channel is still valid for accessing
+ * protocol-specific results. For example, QI'ing to nsIHttpChannel allows
+ * response headers to be retrieved for the corresponding http transaction.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)]
+interface nsIChannel : nsIRequest
+{
+ /**
+ * The original URI used to construct the channel. This is used in
+ * the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null. Attempts to
+ * set it to null must throw.
+ *
+ * NOTE: this is distinctly different from the http Referer (referring URI),
+ * which is typically the page that contained the original URI (accessible
+ * from nsIHttpChannel).
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * The URI corresponding to the channel. Its value is immutable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The owner, corresponding to the entity that is responsible for this
+ * channel. Used by the security manager to grant or deny privileges to
+ * mobile code loaded from this channel.
+ *
+ * NOTE: this is a strong reference to the owner, so if the owner is also
+ * holding a strong reference to the channel, care must be taken to
+ * explicitly drop its reference to the channel.
+ */
+ attribute nsISupports owner;
+
+ /**
+ * The notification callbacks for the channel. This is set by clients, who
+ * wish to provide a means to receive progress, status and protocol-specific
+ * notifications. If this value is NULL, the channel implementation may use
+ * the notification callbacks from its load group. The channel may also
+ * query the notification callbacks from its load group if its notification
+ * callbacks do not supply the requested interface.
+ *
+ * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt,
+ * and nsIAuthPrompt/nsIAuthPrompt2.
+ *
+ * When the channel is done, it must not continue holding references to
+ * this object.
+ *
+ * NOTE: A channel implementation should take care when "caching" an
+ * interface pointer queried from its notification callbacks. If the
+ * notification callbacks are changed, then a cached interface pointer may
+ * become invalid and may therefore need to be re-queried.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any) corresponding to the
+ * channel.
+ *
+ * NOTE: In some circumstances TLS information is propagated onto
+ * non-nsIHttpChannel objects to indicate that their contents were likely
+ * delivered over TLS all the same. For example, document.open() may
+ * create an nsWyciwygChannel to store the data that will be written to the
+ * document. In that case, if the caller has TLS information, we propagate
+ * that info onto the nsWyciwygChannel given that it is likely that the
+ * caller will be writing data that was delivered over TLS to the document.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * The MIME type of the channel's content if available.
+ *
+ * NOTE: the content type can often be wrongly specified (e.g., wrong file
+ * extension, wrong MIME type, wrong document type stored on a server, etc.),
+ * and the caller most likely wants to verify with the actual data.
+ *
+ * Setting contentType before the channel has been opened provides a hint
+ * to the channel as to what the MIME type is. The channel may ignore this
+ * hint in deciding on the actual MIME type that it will report.
+ *
+ * Setting contentType after onStartRequest has been fired or after open()
+ * is called will override the type determined by the channel.
+ *
+ * Setting contentType between the time that asyncOpen() is called and the
+ * time when onStartRequest is fired has undefined behavior at this time.
+ *
+ * The value of the contentType attribute is a lowercase string. A value
+ * assigned to this attribute will be parsed and normalized as follows:
+ * 1- any parameters (delimited with a ';') will be stripped.
+ * 2- if a charset parameter is given, then its value will replace the
+ * the contentCharset attribute of the channel.
+ * 3- the stripped contentType will be lowercased.
+ * Any implementation of nsIChannel must follow these rules.
+ */
+ attribute ACString contentType;
+
+ /**
+ * The character set of the channel's content if available and if applicable.
+ * This attribute only applies to textual data.
+ *
+ * The value of the contentCharset attribute is a mixedcase string.
+ */
+ attribute ACString contentCharset;
+
+ /**
+ * The length of the data associated with the channel if available. A value
+ * of -1 indicates that the content length is unknown. Note that this is a
+ * 64-bit value and obsoletes the "content-length" property used on some
+ * channels.
+ */
+ attribute int64_t contentLength;
+
+ /**
+ * Synchronously open the channel.
+ *
+ * @return blocking input stream to the channel's data.
+ *
+ * NOTE: nsIChannel implementations are not required to implement this
+ * method. Moreover, since this method may block the calling thread, it
+ * should not be called on a thread that processes UI events. Like any
+ * other nsIChannel method it must not be called on any thread other
+ * than the XPCOM main thread.
+ *
+ * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel
+ * is reopened.
+ */
+ nsIInputStream open();
+
+ /**
+ * Performs content security check and calls open()
+ */
+ nsIInputStream open2();
+
+ /**
+ * Asynchronously open this channel. Data is fed to the specified stream
+ * listener as it becomes available. The stream listener's methods are
+ * called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * channel promises to call at least onStartRequest and onStopRequest.
+ *
+ * If the nsIRequest object passed to the stream listener's methods is not
+ * this channel, an appropriate onChannelRedirect notification needs to be
+ * sent to the notification callbacks before onStartRequest is called.
+ * Once onStartRequest is called, all following method calls on aListener
+ * will get the request that was passed to onStartRequest.
+ *
+ * If the channel's and loadgroup's notification callbacks do not provide
+ * an nsIChannelEventSink when onChannelRedirect would be called, that's
+ * equivalent to having called onChannelRedirect.
+ *
+ * If asyncOpen returns successfully, the channel is responsible for
+ * keeping itself alive until it has called onStopRequest on aListener or
+ * called onChannelRedirect.
+ *
+ * Implementations are allowed to synchronously add themselves to the
+ * associated load group (if any).
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * channel is reopened.
+ *
+ * @param aListener the nsIStreamListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ * @see nsIChannelEventSink for onChannelRedirect
+ */
+ void asyncOpen(in nsIStreamListener aListener, in nsISupports aContext);
+
+ /**
+ * Performs content security check and calls asyncOpen().
+ */
+ void asyncOpen2(in nsIStreamListener aListener);
+
+ /**************************************************************************
+ * Channel specific load flags:
+ *
+ * Bits 26-31 are reserved for future use by this interface or one of its
+ * derivatives (e.g., see nsICachingChannel).
+ */
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to a document URI.
+ */
+ const unsigned long LOAD_DOCUMENT_URI = 1 << 16;
+
+ /**
+ * If the end consumer for this load has been retargeted after discovering
+ * its content, this flag will be set:
+ */
+ const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17;
+
+ /**
+ * This flag is set to indicate that this channel is replacing another
+ * channel. This means that:
+ *
+ * 1) the stream listener this channel will be notifying was initially
+ * passed to the asyncOpen method of some other channel
+ *
+ * and
+ *
+ * 2) this channel's URI is a better identifier of the resource being
+ * accessed than this channel's originalURI.
+ *
+ * This flag can be set, for example, for redirects or for cases when a
+ * single channel has multiple parts to it (and thus can follow
+ * onStopRequest with another onStartRequest/onStopRequest pair, each pair
+ * for a different request).
+ */
+ const unsigned long LOAD_REPLACE = 1 << 18;
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to an initial document URI load (e.g., link click).
+ */
+ const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19;
+
+ /**
+ * Set (e.g., by the URILoader) to indicate whether or not the end consumer
+ * for this load has been determined.
+ */
+ const unsigned long LOAD_TARGETED = 1 << 20;
+
+ /**
+ * If this flag is set, the channel should call the content sniffers as
+ * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY.
+ *
+ * Note: Channels may ignore this flag; however, new channel implementations
+ * should only do so with good reason.
+ */
+ const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21;
+
+ /**
+ * This flag tells the channel to use URI classifier service to check
+ * the URI when opening the channel.
+ */
+ const unsigned long LOAD_CLASSIFY_URI = 1 << 22;
+
+ /**
+ * If this flag is set, the media-type content sniffer will be allowed
+ * to override any server-set content-type. Otherwise it will only
+ * be allowed to override "no content type" and application/octet-stream.
+ */
+ const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23;
+
+ /**
+ * Set to let explicitely provided credentials be used over credentials
+ * we have cached previously. In some situations like form login using HTTP
+ * auth via XMLHttpRequest we need to let consumers override the cached
+ * credentials explicitely. For form login 403 response instead of 401 is
+ * usually used to prevent an auth dialog. But any code other then 401/7
+ * will leave original credentials in the cache and there is then no way
+ * to override them for the same user name.
+ */
+ const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24;
+
+ /**
+ * Set to force bypass of any service worker interception of the channel.
+ */
+ const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25;
+
+ // nsICachingChannel load flags begin at bit 26.
+
+ /**
+ * Access to the type implied or stated by the Content-Disposition header
+ * if available and if applicable. This allows determining inline versus
+ * attachment.
+ *
+ * Setting contentDisposition provides a hint to the channel about the
+ * disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will
+ * be used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty, and return
+ * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present.
+ */
+ attribute unsigned long contentDisposition;
+ const unsigned long DISPOSITION_INLINE = 0;
+ const unsigned long DISPOSITION_ATTACHMENT = 1;
+
+ /**
+ * Access to the filename portion of the Content-Disposition header if
+ * available and if applicable. This allows getting the preferred filename
+ * without having to parse it out yourself.
+ *
+ * Setting contentDispositionFilename provides a hint to the channel about
+ * the disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will be
+ * used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't
+ * exist for this type of channel, if the header is empty, if the header
+ * doesn't contain a filename portion, or the value of the filename
+ * attribute is empty/missing.
+ */
+ attribute AString contentDispositionFilename;
+
+ /**
+ * Access to the raw Content-Disposition header if available and applicable.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty.
+ *
+ * @deprecated Use contentDisposition/contentDispositionFilename instead.
+ */
+ readonly attribute ACString contentDispositionHeader;
+
+ /**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+ attribute nsILoadInfo loadInfo;
+
+%{ C++
+ inline already_AddRefed<nsILoadInfo> GetLoadInfo()
+ {
+ nsCOMPtr<nsILoadInfo> result;
+ mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || !result);
+ return result.forget();
+ }
+%}
+
+};
diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl
new file mode 100644
index 000000000..64c5b9eec
--- /dev/null
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implement this interface to receive control over various channel events.
+ * Channels will try to get this interface from a channel's
+ * notificationCallbacks or, if not available there, from the loadGroup's
+ * notificationCallbacks.
+ *
+ * These methods are called before onStartRequest.
+ */
+[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)]
+interface nsIChannelEventSink : nsISupports
+{
+ /**
+ * This is a temporary redirect. New requests for this resource should
+ * continue to use the URI of the old channel.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_TEMPORARY = 1 << 0;
+
+ /**
+ * This is a permanent redirect. New requests for this resource should use
+ * the URI of the new channel (This might be an HTTP 301 reponse).
+ * If this flag is not set, this is a temporary redirect.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_PERMANENT = 1 << 1;
+
+ /**
+ * This is an internal redirect, i.e. it was not initiated by the remote
+ * server, but is specific to the channel implementation.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_INTERNAL = 1 << 2;
+
+ /**
+ * This is a special-cased redirect coming from hitting HSTS upgrade
+ * redirect from http to https only. In some cases this type of redirect
+ * may be considered as safe despite not being the-same-origin redirect.
+ */
+ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
+
+ /**
+ * Called when a redirect occurs. This may happen due to an HTTP 3xx status
+ * code. The purpose of this method is to notify the sink that a redirect
+ * is about to happen, but also to give the sink the right to veto the
+ * redirect by throwing or passing a failure-code in the callback.
+ *
+ * Note that vetoing the redirect simply means that |newChannel| will not
+ * be opened. It is important to understand that |oldChannel| will continue
+ * loading as if it received a HTTP 200, which includes notifying observers
+ * and possibly display or process content attached to the HTTP response.
+ * If the sink wants to prevent this loading it must explicitly deal with
+ * it, e.g. by calling |oldChannel->Cancel()|
+ *
+ * There is a certain freedom in implementing this method:
+ *
+ * If the return-value indicates success, a callback on |callback| is
+ * required. This callback can be done from within asyncOnChannelRedirect
+ * (effectively making the call synchronous) or at some point later
+ * (making the call asynchronous). Repeat: A callback must be done
+ * if this method returns successfully.
+ *
+ * If the return value indicates error (method throws an exception)
+ * the redirect is vetoed and no callback must be done. Repeat: No
+ * callback must be done if this method throws!
+ *
+ * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback()
+ *
+ * @param oldChannel
+ * The channel that's being redirected.
+ * @param newChannel
+ * The new channel. This channel is not opened yet.
+ * @param flags
+ * Flags indicating the type of redirect. A bitmask consisting
+ * of flags from above.
+ * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be
+ * set.
+ * @param callback
+ * Object to inform about the async result of this method
+ *
+ * @throw <any> Throwing an exception will cause the redirect to be
+ * cancelled
+ */
+ void asyncOnChannelRedirect(in nsIChannel oldChannel,
+ in nsIChannel newChannel,
+ in unsigned long flags,
+ in nsIAsyncVerifyRedirectCallback callback);
+};
diff --git a/netwerk/base/nsIChannelWithDivertableParentListener.idl b/netwerk/base/nsIChannelWithDivertableParentListener.idl
new file mode 100644
index 000000000..1fb52931f
--- /dev/null
+++ b/netwerk/base/nsIChannelWithDivertableParentListener.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class ADivertableParentChannel;
+}
+}
+%}
+
+[ptr] native ADivertableParentChannelPtr(mozilla::net::ADivertableParentChannel);
+
+/** When we are diverting messages from the child to the parent. The
+ * nsHttpChannel and nsFtpChannel must know that there is a ChannelParent to
+ * be able to suspend message delivery if the channel is suspended.
+ */
+[uuid(c073d79f-2503-4dff-ba87-d3071f8b433b)]
+interface nsIChannelWithDivertableParentListener : nsISupports
+{
+ /**
+ * Informs nsHttpChannel or nsFtpChannel that a ParentChannel starts
+ * diverting messages. During this time all suspend/resume calls to the
+ * channel must also suspend the ParentChannel by calling
+ * SuspendMessageDiversion/ResumeMessageDiversion.
+ */
+ void MessageDiversionStarted(in ADivertableParentChannelPtr aParentChannel);
+
+ /**
+ * The message diversion has finished the calls to
+ * SuspendMessageDiversion/ResumeMessageDiversion are not necessary anymore.
+ */
+ void MessageDiversionStop();
+
+ /**
+ * Internal versions of Suspend/Resume that suspend (or resume) the channel
+ * but do not suspend the ParentChannel's IPDL message queue.
+ */
+ void SuspendInternal();
+ void ResumeInternal();
+};
diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl
new file mode 100644
index 000000000..fae77f322
--- /dev/null
+++ b/netwerk/base/nsIChildChannel.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * Implemented by content side of IPC protocols.
+ */
+
+[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)]
+interface nsIChildChannel : nsISupports
+{
+ /**
+ * Create the chrome side of the IPC protocol and join an existing 'real'
+ * channel on the parent process. The id is provided by
+ * nsIRedirectChannelRegistrar on the chrome process and pushed to the child
+ * protocol as an argument to event starting a redirect.
+ *
+ * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created
+ * child channel, where the new channel is intended to be created on the
+ * child process.
+ */
+ void connectParent(in uint32_t registrarId);
+
+ /**
+ * As AsyncOpen is called on the chrome process for redirect target channels,
+ * we have to inform the child side of the protocol of that fact by a special
+ * method.
+ */
+ void completeRedirectSetup(in nsIStreamListener aListener,
+ in nsISupports aContext);
+};
diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl
new file mode 100644
index 000000000..30590b324
--- /dev/null
+++ b/netwerk/base/nsIClassOfService.idl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassOfService.idl
+ *
+ * Used to express class dependencies and characteristics - complimentary to
+ * nsISupportsPriority which is used to express weight
+ *
+ * Channels that implement this interface may make use of this
+ * information in different ways.
+ *
+ * The default gecko HTTP/1 stack makes Followers wait for Leaders to
+ * complete before dispatching followers. Other classes run in
+ * parallel - neither being blocked nor blocking. All grouping is done
+ * based on the Load Group - separate load groups proceed
+ * independently.
+ *
+ * HTTP/2 does not use the load group, but prioritization is done per
+ * HTTP/2 session. HTTP/2 dispatches all the requests as soon as
+ * possible.
+ * The various classes are assigned logical priority
+ * dependency groups and then transactions of that class depend on the
+ * group. In this model Followers block on Leaders and Speculative
+ * depends on Background. See Http2Stream.cpp for weighting details.
+ *
+ */
+
+[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)]
+interface nsIClassOfService : nsISupports
+{
+ attribute unsigned long classFlags;
+
+ void clearClassFlags(in unsigned long flags);
+ void addClassFlags(in unsigned long flags);
+
+ const unsigned long Leader = 1 << 0;
+ const unsigned long Follower = 1 << 1;
+ const unsigned long Speculative = 1 << 2;
+ const unsigned long Background = 1 << 3;
+ const unsigned long Unblocked = 1 << 4;
+ const unsigned long Throttleable = 1 << 5;
+ const unsigned long UrgentStart = 1 << 6;
+};
diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl
new file mode 100644
index 000000000..f9052b8e6
--- /dev/null
+++ b/netwerk/base/nsIContentSniffer.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * Content sniffer interface. Components implementing this interface can
+ * determine a MIME type from a chunk of bytes.
+ */
+[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)]
+interface nsIContentSniffer : nsISupports
+{
+ /**
+ * Given a chunk of data, determines a MIME type. Information from the given
+ * request may be used in order to make a better decision.
+ *
+ * @param aRequest The request where this data came from. May be null.
+ * @param aData Data to check
+ * @param aLength Length of the data
+ *
+ * @return The content type
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined.
+ *
+ * @note Implementations should consider the request read-only. Especially,
+ * they should not attempt to set the content type property that subclasses of
+ * nsIRequest might offer.
+ */
+ ACString getMIMETypeFromContent(in nsIRequest aRequest,
+ [const,array,size_is(aLength)] in octet aData,
+ in unsigned long aLength);
+};
diff --git a/netwerk/base/nsICryptoFIPSInfo.idl b/netwerk/base/nsICryptoFIPSInfo.idl
new file mode 100644
index 000000000..1defc56ab
--- /dev/null
+++ b/netwerk/base/nsICryptoFIPSInfo.idl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(99e81922-7318-4431-b3aa-78b3cb4119bb)]
+interface nsICryptoFIPSInfo : nsISupports
+{
+ readonly attribute boolean isFIPSModeActive;
+};
+
+%{C++
+#define NS_CRYPTO_FIPSINFO_SERVICE_CONTRACTID "@mozilla.org/crypto/fips-info-service;1"
+%}
diff --git a/netwerk/base/nsICryptoHMAC.idl b/netwerk/base/nsICryptoHMAC.idl
new file mode 100644
index 000000000..92e06a0a4
--- /dev/null
+++ b/netwerk/base/nsICryptoHMAC.idl
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIInputStream;
+interface nsIKeyObject;
+
+/**
+ * nsICryptoHMAC
+ * This interface provides HMAC signature algorithms.
+ */
+
+[scriptable, uuid(8FEB4C7C-1641-4a7b-BC6D-1964E2099497)]
+interface nsICryptoHMAC : nsISupports
+{
+ /**
+ * Hashing Algorithms. These values are to be used by the
+ * |init| method to indicate which hashing function to
+ * use. These values map onto the values defined in
+ * mozilla/security/nss/lib/softoken/pkcs11t.h and are
+ * switched to CKM_*_HMAC constant.
+ */
+ const short MD2 = 1;
+ const short MD5 = 2;
+ const short SHA1 = 3;
+ const short SHA256 = 4;
+ const short SHA384 = 5;
+ const short SHA512 = 6;
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ * This value must be one of the above valid
+ * algorithm types.
+ *
+ * @param aKeyObject
+ * Object holding a key. To create the key object use for instance:
+ * var keyObject = Components.classes["@mozilla.org/security/keyobjectfactory;1"]
+ * .getService(Components.interfaces.nsIKeyObjectFactory)
+ * .keyFromString(Components.interfaces.nsIKeyObject.HMAC, rawKeyData);
+ *
+ * WARNING: This approach is not FIPS compliant.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method must be called before any other method
+ * on this interface is called.
+ */
+ void init(in unsigned long aAlgorithm, in nsIKeyObject aKeyObject);
+
+ /**
+ * @param aData a buffer to calculate the hash over
+ *
+ * @param aLen the length of the buffer |aData|
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ */
+ void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen);
+
+ /**
+ * Calculates and updates a new hash based on a given data stream.
+ *
+ * @param aStream an input stream to read from.
+ *
+ * @param aLen how much to read from the given |aStream|. Passing
+ * UINT32_MAX indicates that all data available will be used
+ * to update the hash.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of
+ * data to be calculated into the hash is not available.
+ *
+ */
+ void updateFromStream(in nsIInputStream aStream, in unsigned long aLen);
+
+ /**
+ * Completes this HMAC object and produces the actual HMAC diegest data.
+ *
+ * @param aASCII if true then the returned value is a base-64
+ * encoded string. if false, then the returned value is
+ * binary data.
+ *
+ * @return a hash of the data that was read by this object. This can
+ * be either binary data or base 64 encoded.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * NOTE: This method may be called any time after |init|
+ * is called. This call resets the object to its
+ * pre-init state.
+ */
+ ACString finish(in boolean aASCII);
+
+ /**
+ * Reinitialize HMAC context to be reused with the same
+ * settings (the key and hash algorithm) but on different
+ * set of data.
+ */
+ void reset();
+};
diff --git a/netwerk/base/nsICryptoHash.idl b/netwerk/base/nsICryptoHash.idl
new file mode 100644
index 000000000..cd865a3a9
--- /dev/null
+++ b/netwerk/base/nsICryptoHash.idl
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIInputStream;
+
+/**
+ * nsICryptoHash
+ * This interface provides crytographic hashing algorithms.
+ */
+
+[scriptable, uuid(1e5b7c43-4688-45ce-92e1-77ed931e3bbe)]
+interface nsICryptoHash : nsISupports
+{
+ /**
+ * Hashing Algorithms. These values are to be used by the
+ * |init| method to indicate which hashing function to
+ * use. These values map directly onto the values defined
+ * in mozilla/security/nss/lib/cryptohi/hasht.h.
+ */
+ const short MD2 = 1; /* String value: "md2" */
+ const short MD5 = 2; /* String value: "md5" */
+ const short SHA1 = 3; /* String value: "sha1" */
+ const short SHA256 = 4; /* String value: "sha256" */
+ const short SHA384 = 5; /* String value: "sha384" */
+ const short SHA512 = 6; /* String value: "sha512" */
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ * This value must be one of the above valid
+ * algorithm types.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method or initWithString must be called
+ * before any other method on this interface is called.
+ */
+ void init(in unsigned long aAlgorithm);
+
+ /**
+ * Initialize the hashing object. This method may be
+ * called multiple times with different algorithm types.
+ *
+ * @param aAlgorithm the algorithm type to be used.
+ *
+ * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm
+ * type is passed.
+ *
+ * NOTE: This method or init must be called before any
+ * other method on this interface is called.
+ */
+ void initWithString(in ACString aAlgorithm);
+
+ /**
+ * @param aData a buffer to calculate the hash over
+ *
+ * @param aLen the length of the buffer |aData|
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ */
+ void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen);
+
+ /**
+ * Calculates and updates a new hash based on a given data stream.
+ *
+ * @param aStream an input stream to read from.
+ *
+ * @param aLen how much to read from the given |aStream|. Passing
+ * UINT32_MAX indicates that all data available will be used
+ * to update the hash.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of
+ * data to be calculated into the hash is not available.
+ *
+ */
+ void updateFromStream(in nsIInputStream aStream, in unsigned long aLen);
+
+ /**
+ * Completes this hash object and produces the actual hash data.
+ *
+ * @param aASCII if true then the returned value is a base-64
+ * encoded string. if false, then the returned value is
+ * binary data.
+ *
+ * @return a hash of the data that was read by this object. This can
+ * be either binary data or base 64 encoded.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been
+ * called.
+ *
+ * NOTE: This method may be called any time after |init|
+ * is called. This call resets the object to its
+ * pre-init state.
+ */
+ ACString finish(in boolean aASCII);
+};
diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl
new file mode 100644
index 000000000..a18710e5a
--- /dev/null
+++ b/netwerk/base/nsIDashboard.idl
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* A JavaScript callback function that takes a JSON as its parameter.
+ * The returned JSON contains arrays with data
+ */
+[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)]
+interface NetDashboardCallback : nsISupports
+{
+ void onDashboardDataAvailable(in jsval data);
+};
+
+/* The dashboard service.
+ * The async API returns JSONs, which hold arrays with the required info.
+ * Only one request of each type may be pending at any time.
+ */
+[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)]
+interface nsIDashboard : nsISupports
+{
+ /* Arrays: host, port, tcp, active, socksent, sockreceived
+ * Values: sent, received */
+ void requestSockets(in NetDashboardCallback cb);
+
+ /* Arrays: host, port, spdy, ssl
+ * Array of arrays: active, idle */
+ void requestHttpConnections(in NetDashboardCallback cb);
+
+ /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */
+ void requestWebsocketConnections(in NetDashboardCallback cb);
+
+ /* Arrays: hostname, family, hostaddr, expiration */
+ void requestDNSInfo(in NetDashboardCallback cb);
+
+ /* aProtocol: a transport layer protocol:
+ * ex: "ssl", "tcp", default is "tcp".
+ * aHost: the host's name
+ * aPort: the port which the connection will open on
+ * aTimeout: the timespan before the connection will be timed out */
+ void requestConnection(in ACString aHost, in unsigned long aPort,
+ in string aProtocol, in unsigned long aTimeout,
+ in NetDashboardCallback cb);
+
+ /* When true, the service will log websocket events */
+ attribute boolean enableLogging;
+
+ /* DNS resolver for host name
+ * aHost: host name */
+ void requestDNSLookup(in ACString aHost, in NetDashboardCallback cb);
+
+ AUTF8String getLogPath();
+};
diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl
new file mode 100644
index 000000000..d84fd2ed4
--- /dev/null
+++ b/netwerk/base/nsIDashboardEventNotifier.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[builtinclass, uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)]
+interface nsIDashboardEventNotifier : nsISupports
+{
+ /* These methods are called to register a websocket event with the dashboard
+ *
+ * A host is identified by the (aHost, aSerial) pair.
+ * aHost: the host's name: example.com
+ * aSerial: a number that uniquely identifies the websocket
+ *
+ * aEncrypted: if the connection is encrypted
+ * aLength: the length of the message in bytes
+ */
+ void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted);
+ void removeHost(in ACString aHost, in unsigned long aSerial);
+ void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+ void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDeprecationWarner.idl b/netwerk/base/nsIDeprecationWarner.idl
new file mode 100644
index 000000000..72303b815
--- /dev/null
+++ b/netwerk/base/nsIDeprecationWarner.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface for warning about deprecated operations. Consumers should
+ * attach this interface to the channel's notification callbacks/loadgroup.
+ */
+[uuid(665c5124-2c52-41ba-ae72-2393f8e76c25)]
+interface nsIDeprecationWarner : nsISupports
+{
+ /**
+ * Issue a deprecation warning.
+ *
+ * @param aWarning a warning code as declared in nsDeprecatedOperationList.h.
+ * @param aAsError optional boolean flag indicating whether the warning
+ * should be treated as an error.
+ */
+ void issueWarning(in uint32_t aWarning, [optional] in bool aAsError);
+};
diff --git a/netwerk/base/nsIDivertableChannel.idl b/netwerk/base/nsIDivertableChannel.idl
new file mode 100644
index 000000000..4c28ca7dc
--- /dev/null
+++ b/netwerk/base/nsIDivertableChannel.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+namespace net {
+class ChannelDiverterChild;
+}
+}
+%}
+
+[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild);
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows diverting from an
+ * nsIStreamListener in the child process to one in the parent.
+ */
+[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)]
+interface nsIDivertableChannel : nsISupports
+{
+ /**
+ * CHILD ONLY.
+ * Called by Necko client in child process during OnStartRequest to divert
+ * nsIStreamListener and nsIRequest callbacks to the parent process.
+ *
+ * The process should look like the following:
+ *
+ * 1) divertToParent is called in the child process. It can only be called
+ * during OnStartRequest().
+ *
+ * 2) The ChannelDiverterChild that is returned is an IPDL object. It should
+ * be passed via some other IPDL method of the client's choosing to the
+ * parent. On the parent the ChannelDiverterParent's divertTo() function
+ * should be called with an nsIStreamListener that will then receive the
+ * OnStartRequest/OnDataAvailable/OnStopRequest for the channel. The
+ * ChannelDiverterParent can then be deleted (which will also destroy the
+ * ChannelDiverterChild in the child).
+ *
+ * After divertToParent() has been called, NO further function calls
+ * should be made on the channel. It is a dead object for all purposes.
+ * The reference that the channel holds to the listener in the child is
+ * released is once OnStartRequest completes, and no other
+ * nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made
+ * to it.
+ *
+ * @return ChannelDiverterChild IPDL actor to be passed to parent process by
+ * client IPDL message, e.g. PClient.DivertUsing(PDiverterChild).
+ *
+ * @throws exception if the channel was canceled early. Throws status code of
+ * canceled channel.
+ */
+ ChannelDiverterChild divertToParent();
+
+ /**
+ * nsUnknownDecoder delays calling OnStartRequest until it gets enough data
+ * to decide about the content type (until OnDataAvaiable is called). In a
+ * OnStartRequest DivertToParent can be called but some OnDataAvailables are
+ * already called and therefore can not be diverted to parent.
+ *
+ * nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its
+ * OnStartRequest function and when it calls OnStartRequest of the next
+ * listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this
+ * function Child process will decide to discarge data if it is not diverting
+ * to parent or keep them if it is diverting to parent.
+ */
+ void unknownDecoderInvolvedKeepData();
+
+ void unknownDecoderInvolvedOnStartRequestCalled();
+
+ readonly attribute bool divertingToParent;
+};
diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl
new file mode 100644
index 000000000..738940b4b
--- /dev/null
+++ b/netwerk/base/nsIDownloader.idl
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIFile;
+interface nsIDownloadObserver;
+
+/**
+ * nsIDownloader
+ *
+ * A downloader is a special implementation of a nsIStreamListener that will
+ * make the contents of the stream available as a file. This may utilize the
+ * disk cache as an optimization to avoid an extra copy of the data on disk.
+ * The resulting file is valid from the time the downloader completes until
+ * the last reference to the downloader is released.
+ */
+[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)]
+interface nsIDownloader : nsIStreamListener
+{
+ /**
+ * Initialize this downloader
+ *
+ * @param observer
+ * the observer to be notified when the download completes.
+ * @param downloadLocation
+ * the location where the stream contents should be written.
+ * if null, the downloader will select a location and the
+ * resulting file will be deleted (or otherwise made invalid)
+ * when the downloader object is destroyed. if an explicit
+ * download location is specified then the resulting file will
+ * not be deleted, and it will be the callers responsibility
+ * to keep track of the file, etc.
+ */
+ void init(in nsIDownloadObserver observer,
+ in nsIFile downloadLocation);
+};
+
+[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)]
+interface nsIDownloadObserver : nsISupports
+{
+ /**
+ * Called to signal a download that has completed.
+ */
+ void onDownloadComplete(in nsIDownloader downloader,
+ in nsIRequest request,
+ in nsISupports ctxt,
+ in nsresult status,
+ in nsIFile result);
+};
diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl
new file mode 100644
index 000000000..77cee0d28
--- /dev/null
+++ b/netwerk/base/nsIEncodedChannel.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUTF8StringEnumerator;
+interface nsIStreamListener;
+interface nsISupports;
+
+/**
+ * A channel interface which allows special handling of encoded content
+ */
+
+[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)]
+interface nsIEncodedChannel : nsISupports
+{
+ /**
+ * This attribute holds the MIME types corresponding to the content
+ * encodings on the channel. The enumerator returns nsISupportsCString
+ * objects. The first one corresponds to the outermost encoding on the
+ * channel and then we work our way inward. "identity" is skipped and not
+ * represented on the list. Unknown encodings make the enumeration stop.
+ * If you want the actual Content-Encoding value, use
+ * getResponseHeader("Content-Encoding").
+ *
+ * When there is no Content-Encoding header, this property is null.
+ *
+ * Modifying the Content-Encoding header on the channel will cause
+ * this enumerator to have undefined behavior. Don't do it.
+ *
+ * Also note that contentEncodings only exist during or after OnStartRequest.
+ * Calling contentEncodings before OnStartRequest is an error.
+ */
+ readonly attribute nsIUTF8StringEnumerator contentEncodings;
+
+ /**
+ * This attribute controls whether or not content conversion should be
+ * done per the Content-Encoding response header. applyConversion can only
+ * be set before or during OnStartRequest. Calling this during
+ * OnDataAvailable is an error.
+ *
+ * TRUE by default.
+ */
+ attribute boolean applyConversion;
+
+ /**
+ * This function will start converters if they are available.
+ * aNewNextListener will be nullptr if no converter is available.
+ */
+ void doApplyContentConversions(in nsIStreamListener aNextListener,
+ out nsIStreamListener aNewNextListener,
+ in nsISupports aCtxt);
+};
diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl
new file mode 100644
index 000000000..6f86dbb05
--- /dev/null
+++ b/netwerk/base/nsIExternalProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)]
+interface nsIExternalProtocolHandler: nsIProtocolHandler
+{
+ /**
+ * This method checks if the external handler exists for a given scheme.
+ *
+ * @param scheme external scheme.
+ * @return TRUE if the external handler exists for the input scheme, FALSE otherwise.
+ */
+ boolean externalAppExistsForScheme(in ACString scheme);
+};
diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl
new file mode 100644
index 000000000..e5f357f6a
--- /dev/null
+++ b/netwerk/base/nsIFileStreams.idl
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+interface nsIFile;
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+
+/**
+ * An input stream that allows you to read from a file.
+ */
+[scriptable, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)]
+interface nsIFileInputStream : nsIInputStream
+{
+ /**
+ * @param file file to read from
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * If this is set, the file will be deleted by the time the stream is
+ * closed. It may be removed before the stream is closed if it is possible
+ * to delete it and still read from it.
+ *
+ * If OPEN_ON_READ is defined, and the file was recreated after the first
+ * delete, the file will be deleted again when it is closed again.
+ */
+ const long DELETE_ON_CLOSE = 1<<1;
+
+ /**
+ * If this is set, the file will close automatically when the end of the
+ * file is reached.
+ */
+ const long CLOSE_ON_EOF = 1<<2;
+
+ /**
+ * If this is set, the file will be reopened whenever we reach the start of
+ * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
+ * seek that happen to reach the beginning of the file. If the file is
+ * already open and the seek occurs, it will happen naturally. (The file
+ * will only be reopened if it is closed for some reason.)
+ */
+ const long REOPEN_ON_REWIND = 1<<3;
+
+ /**
+ * If this is set, the file will be opened (i.e., a call to
+ * PR_Open done) only when we do an actual operation on the stream,
+ * or more specifically, when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - ReadLine
+ *
+ * DEFER_OPEN is useful if we use the stream on a background
+ * thread, so that the opening and possible |stat|ing of the file
+ * happens there as well.
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read. Also, the file is not locked when Init is called,
+ * so it might be deleted before we try to read from it.
+ */
+ const long DEFER_OPEN = 1<<4;
+
+ /**
+ * This flag has no effect and is totally ignored on any platform except
+ * Windows since this is the default behavior on POSIX systems. On Windows
+ * if this flag is set then the stream is opened in a special mode that
+ * allows the OS to delete the file from disk just like POSIX.
+ */
+ const long SHARE_DELETE = 1<<5;
+};
+
+/**
+ * An output stream that lets you stream to a file.
+ */
+[scriptable, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)]
+interface nsIFileOutputStream : nsIOutputStream
+{
+ /**
+ * @param file file to write to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_WRONLY |
+ * PR_CREATE_FILE | PR_TRUNCATE)
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default permissions (0664)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (currently none supported)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * @param length asks the operating system to allocate storage for
+ * this file of at least |length| bytes long, and
+ * set the file length to the corresponding size.
+ * @throws NS_ERROR_FAILURE if the preallocation fails.
+ * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened.
+ */
+ [noscript] void preallocate(in long long length);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Write
+ * - Flush
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first write, and if the file is to be created, then it will not
+ * appear on the disk until the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An input stream that allows you to read from a slice of a file.
+ */
+[scriptable, uuid(3ce03a2f-97f7-4375-b6bb-1788a60cad3b)]
+interface nsIPartialFileInputStream : nsISupports
+{
+ /**
+ * Initialize with a file and new start/end positions. Both start and
+ * start+length must be smaller than the size of the file. Not doing so
+ * will lead to undefined behavior.
+ * You must initialize the stream, and only initialize it once, before it
+ * can be used.
+ *
+ * @param file file to read from
+ * @param start start offset of slice to read. Must be smaller
+ * than the size of the file.
+ * @param length length of slice to read. Must be small enough that
+ * start+length is smaller than the size of the file.
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in nsIFileInputStream)
+ */
+ void init(in nsIFile file, in unsigned long long start,
+ in unsigned long long length,
+ in long ioFlags, in long perm, in long behaviorFlags);
+};
+
+/**
+ * A stream that allows you to read from a file or stream to a file.
+ */
+[scriptable, uuid(82cf605a-8393-4550-83ab-43cd5578e006)]
+interface nsIFileStream : nsISupports
+{
+ /**
+ * @param file file to read from or stream to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDWR).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - Flush
+ * - Write
+ * - GetSize
+ * - GetLastModified
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read or write. The file is not locked when Init is called,
+ * so it might be deleted before we try to read from it and if the
+ * file is to be created, then it will not appear on the disk until
+ * the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An interface that allows you to get some metadata like file size and
+ * file last modified time.
+ */
+[scriptable, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)]
+interface nsIFileMetadata : nsISupports
+{
+ /**
+ * File size in bytes;
+ */
+ readonly attribute long long size;
+
+ /**
+ * File last modified time in milliseconds from midnight (00:00:00),
+ * January 1, 1970 Greenwich Mean Time (GMT).
+ */
+ readonly attribute long long lastModified;
+
+ /**
+ * The internal file descriptor. It can be used for memory mapping of the
+ * underlying file. Please use carefully!
+ */
+ [noscript] PRFileDescPtr getFileDescriptor();
+};
diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl
new file mode 100644
index 000000000..9f8b67cb8
--- /dev/null
+++ b/netwerk/base/nsIFileURL.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.idl"
+
+interface nsIFile;
+
+/**
+ * nsIFileURL provides access to the underlying nsIFile object corresponding to
+ * an URL. The URL scheme need not be file:, since other local protocols may
+ * map URLs to files (e.g., resource:).
+ */
+[scriptable, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)]
+interface nsIFileURL : nsIURL
+{
+ /**
+ * Get/Set nsIFile corresponding to this URL.
+ *
+ * - Getter returns a reference to an immutable object. Callers must clone
+ * before attempting to modify the returned nsIFile object. NOTE: this
+ * constraint might not be enforced at runtime, so beware!!
+ *
+ * - Setter clones the nsIFile object (allowing the caller to safely modify
+ * the nsIFile object after setting it on this interface).
+ */
+ attribute nsIFile file;
+};
diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl
new file mode 100644
index 000000000..a4d6ef894
--- /dev/null
+++ b/netwerk/base/nsIForcePendingChannel.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIForcePending interface exposes a function that enables overwriting of the normal
+ * behavior for the channel's IsPending(), forcing 'true' to be returned.
+ */
+
+[noscript, uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)]
+interface nsIForcePendingChannel : nsISupports
+{
+
+/**
+ * forcePending(true) overrides the normal behavior for the
+ * channel's IsPending(), forcing 'true' to be returned. A call to
+ * forcePending(false) reverts IsPending() back to normal behavior.
+ */
+ void forcePending(in boolean aForcePending);
+};
diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl
new file mode 100644
index 000000000..870886390
--- /dev/null
+++ b/netwerk/base/nsIFormPOSTActionChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUploadChannel.idl"
+
+/**
+ * nsIFormPOSTActionChannel
+ *
+ * Channel classes that want to be allowed for HTML form POST action must
+ * implement this interface.
+ */
+[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)]
+interface nsIFormPOSTActionChannel : nsIUploadChannel
+{
+};
diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl
new file mode 100644
index 000000000..9ce233515
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+ /**
+ * Authentication data for a header is available.
+ *
+ * @param aCreds
+ * Credentials which were obtained asynchonously.
+ * @param aFlags
+ * Flags set by asynchronous call.
+ * @param aResult
+ * Result status of credentials generation
+ * @param aSessionState
+ * Modified session state to be passed to caller
+ * @param aContinuationState
+ * Modified continuation state to be passed to caller
+ */
+ void onCredsGenerated(in string aCreds,
+ in unsigned long aFlags,
+ in nsresult aResult,
+ in nsISupports aSessionsState,
+ in nsISupports aContinuationState);
+
+};
+
diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl
new file mode 100644
index 000000000..f44605c00
--- /dev/null
+++ b/netwerk/base/nsIHttpPushListener.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIHttpChannel;
+
+/**
+ * nsIHttpPushListener
+ *
+ * Used for triggering when a HTTP/2 push is received.
+ *
+ */
+[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)]
+interface nsIHttpPushListener : nsISupports
+{
+ /**
+ * When provided as a notificationCallback to an httpChannel, this.onPush()
+ * will be invoked when there is a >= Http2 push to that
+ * channel. The push may be in progress.
+ *
+ * The consumer must start the new channel in the usual way by calling
+ * pushChannel.AsyncOpen with a nsIStreamListener object that
+ * will receive the normal sequence of OnStartRequest(),
+ * 0 to N OnDataAvailable(), and onStopRequest().
+ *
+ * The new channel can be canceled after the AsyncOpen if it is not wanted.
+ *
+ * @param associatedChannel
+ * the monitor channel that was recieved on
+ * @param pushChannel
+ * a channel to the resource which is being pushed
+ */
+ void onPush(in nsIHttpChannel associatedChannel,
+ in nsIHttpChannel pushChannel);
+};
diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl
new file mode 100644
index 000000000..9bb777405
--- /dev/null
+++ b/netwerk/base/nsIIOService.idl
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProtocolHandler;
+interface nsIChannel;
+interface nsIURI;
+interface nsIFile;
+interface nsIDOMNode;
+interface nsIPrincipal;
+interface nsILoadInfo;
+
+/**
+ * nsIIOService provides a set of network utility functions. This interface
+ * duplicates many of the nsIProtocolHandler methods in a protocol handler
+ * independent way (e.g., NewURI inspects the scheme in order to delegate
+ * creation of the new URI to the appropriate protocol handler). nsIIOService
+ * also provides a set of URL parsing utility functions. These are provided
+ * as a convenience to the programmer and in some cases to improve performance
+ * by eliminating intermediate data structures and interfaces.
+ */
+[scriptable, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)]
+interface nsIIOService : nsISupports
+{
+ /**
+ * Returns a protocol handler for a given URI scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return reference to corresponding nsIProtocolHandler
+ */
+ nsIProtocolHandler getProtocolHandler(in string aScheme);
+
+ /**
+ * Returns the protocol flags for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return value of corresponding nsIProtocolHandler::protocolFlags
+ */
+ unsigned long getProtocolFlags(in string aScheme);
+
+ /**
+ * This method constructs a new URI by determining the scheme of the
+ * URI spec, and then delegating the construction of the URI to the
+ * protocol handler for that scheme. QueryInterface can be used on
+ * the resulting URI object to obtain a more specific type of URI.
+ *
+ * @see nsIProtocolHandler::newURI
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * This method constructs a new URI from a nsIFile.
+ *
+ * @param aFile specifies the file path
+ * @return reference to a new nsIURI object
+ *
+ * Note: in the future, for perf reasons we should allow
+ * callers to specify whether this is a file or directory by
+ * splitting this into newDirURI() and newActualFileURI().
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+ nsIChannel newChannelFromURI2(in nsIURI aURI,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...)
+ */
+ nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * Equivalent to newChannelFromURI2(newURI(...))
+ */
+ nsIChannel newChannel2(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use NewChannelFromURI2()
+ *
+ * Creates a channel for a given URI.
+ *
+ * @param aURI nsIURI from which to make a channel
+ * @return reference to the new nsIChannel object
+ */
+ nsIChannel newChannelFromURI(in nsIURI aURI);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use newChannel2().
+ *
+ * Equivalent to newChannelFromURI(newURI(...))
+ */
+ nsIChannel newChannel(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+
+ /**
+ * Returns true if networking is in "offline" mode. When in offline mode,
+ * attempts to access the network will fail (although this does not
+ * necessarily correlate with whether there is actually a network
+ * available -- that's hard to detect without causing the dialer to
+ * come up).
+ *
+ * Changing this fires observer notifications ... see below.
+ */
+ attribute boolean offline;
+
+ /**
+ * Returns false if there are no interfaces for a network request
+ */
+ readonly attribute boolean connectivity;
+
+ /**
+ * Checks if a port number is banned. This involves consulting a list of
+ * unsafe ports, corresponding to network services that may be easily
+ * exploitable. If the given port is considered unsafe, then the protocol
+ * handler (corresponding to aScheme) will be asked whether it wishes to
+ * override the IO service's decision to block the port. This gives the
+ * protocol handler ultimate control over its own security policy while
+ * ensuring reasonable, default protection.
+ *
+ * @see nsIProtocolHandler::allowPort
+ */
+ boolean allowPort(in long aPort, in string aScheme);
+
+ /**
+ * Utility to extract the scheme from a URL string, consistently and
+ * according to spec (see RFC 2396).
+ *
+ * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme
+ * can also be extracted from a URL string via nsIURI. This method
+ * is provided purely as an optimization.
+ *
+ * @param aSpec the URL string to parse
+ * @return URL scheme
+ *
+ * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
+ */
+ ACString extractScheme(in AUTF8String urlString);
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
+ * when 'offline' has changed from false to true, and we are about
+ * to shut down network services such as DNS. When those
+ * services have been shut down, we send a notification with
+ * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_OFFLINE.
+ *
+ * When 'offline' changes from true to false, then after
+ * network services have been restarted, we send a notification
+ * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_ONLINE.
+ */
+#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
+#define NS_IOSERVICE_OFFLINE "offline"
+#define NS_IOSERVICE_ONLINE "online"
+
+%}
+
+[builtinclass, uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)]
+interface nsIIOServiceInternal : nsISupports
+{
+ /**
+ * This is an internal method that should only be called from ContentChild
+ * in order to pass the connectivity state from the chrome process to the
+ * content process. It throws if called outside the content process.
+ */
+ void SetConnectivity(in boolean connectivity);
+
+ /**
+ * An internal method to asynchronously run our notifications that happen
+ * when we wake from sleep
+ */
+ void NotifyWakeup();
+};
diff --git a/netwerk/base/nsIIOService2.idl b/netwerk/base/nsIIOService2.idl
new file mode 100644
index 000000000..d7b434d17
--- /dev/null
+++ b/netwerk/base/nsIIOService2.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIIOService.idl"
+
+interface nsIDOMNode;
+interface nsIPrincipal;
+
+/**
+ * nsIIOService2 extends nsIIOService
+ */
+[scriptable, uuid(52c5804b-0d3c-4d4f-8654-1c36fd310e69)]
+interface nsIIOService2 : nsIIOService
+{
+ /**
+ * While this is set, IOService will monitor an nsINetworkLinkService
+ * (if available) and set its offline status to "true" whenever
+ * isLinkUp is false.
+ *
+ * Applications that want to control changes to the IOService's offline
+ * status should set this to false, watch for network:link-status-changed
+ * broadcasts, and change nsIIOService::offline as they see fit. Note
+ * that this means during application startup, IOService may be offline
+ * if there is no link, until application code runs and can turn off
+ * this management.
+ */
+ attribute boolean manageOfflineStatus;
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aProxyURI
+ * nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ nsIChannel newChannelFromURIWithProxyFlags2(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags,
+ in nsIDOMNode aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in unsigned long aContentPolicyType);
+
+ /**
+ * ***** DEPRECATED *****
+ * Please use newChannelFromURIWithProxyFlags2()
+ *
+ * Creates a channel for a given URI.
+ *
+ * @param aURI nsIURI from which to make a channel
+ * @param aProxyURI nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @return reference to the new nsIChannel object
+ */
+ nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags);
+
+};
diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl
new file mode 100644
index 000000000..3ae363c48
--- /dev/null
+++ b/netwerk/base/nsIIncrementalDownload.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIRequestObserver;
+
+/**
+ * An incremental download object attempts to fetch a file piecemeal over time
+ * in an effort to minimize network bandwidth usage.
+ *
+ * Canceling a background download does not cause the file on disk to be
+ * deleted.
+ */
+[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)]
+interface nsIIncrementalDownload : nsIRequest
+{
+ /**
+ * Initialize the incremental download object. If the destination file
+ * already exists, then only the remaining portion of the file will be
+ * fetched.
+ *
+ * NOTE: The downloader will create the destination file if it does not
+ * already exist. It will create the file with the permissions 0600 if
+ * needed. To affect the permissions of the file, consumers of this
+ * interface may create an empty file at the specified destination prior to
+ * starting the incremental download.
+ *
+ * NOTE: Since this class may create a temporary file at the specified
+ * destination, it is advisable for the consumer of this interface to specify
+ * a file name for the destination that would not tempt the user into
+ * double-clicking it. For example, it might be wise to append a file
+ * extension like ".part" to the end of the destination to protect users from
+ * accidentally running "blah.exe" before it is a complete file.
+ *
+ * @param uri
+ * The URI to fetch.
+ * @param destination
+ * The location where the file is to be stored.
+ * @param chunkSize
+ * The size of the chunks to fetch. A non-positive value results in
+ * the default chunk size being used.
+ * @param intervalInSeconds
+ * The amount of time to wait between fetching chunks. Pass a
+ * negative to use the default interval, or 0 to fetch the remaining
+ * part of the file in one chunk.
+ */
+ void init(in nsIURI uri, in nsIFile destination, in long chunkSize,
+ in long intervalInSeconds);
+
+ /**
+ * The URI being fetched.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The URI being fetched after any redirects have been followed. This
+ * attribute is set just prior to calling OnStartRequest on the observer
+ * passed to the start method.
+ */
+ readonly attribute nsIURI finalURI;
+
+ /**
+ * The file where the download is being written.
+ */
+ readonly attribute nsIFile destination;
+
+ /**
+ * The total number of bytes for the requested file. This attribute is set
+ * just prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the total size is unknown.
+ */
+ readonly attribute long long totalSize;
+
+ /**
+ * The current number of bytes downloaded so far. This attribute is set just
+ * prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the current size is unknown.
+ */
+ readonly attribute long long currentSize;
+
+ /**
+ * Start the incremental download.
+ *
+ * @param observer
+ * An observer to be notified of various events. OnStartRequest is
+ * called when finalURI and totalSize have been determined or when an
+ * error occurs. OnStopRequest is called when the file is completely
+ * downloaded or when an error occurs. If this object implements
+ * nsIProgressEventSink, then its OnProgress method will be called as
+ * data is written to the destination file. If this object implements
+ * nsIInterfaceRequestor, then it will be assigned as the underlying
+ * channel's notification callbacks, which allows it to provide a
+ * nsIAuthPrompt implementation if needed by the channel, for example.
+ * @param ctxt
+ * User defined object forwarded to the observer's methods.
+ */
+ void start(in nsIRequestObserver observer,
+ in nsISupports ctxt);
+};
diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl
new file mode 100644
index 000000000..60aa9cfef
--- /dev/null
+++ b/netwerk/base/nsIIncrementalStreamLoader.idl
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIRequest;
+interface nsIIncrementalStreamLoader;
+
+[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)]
+interface nsIIncrementalStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when new data has arrived on the stream.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param dataLength the length of the new data received
+ * @param data the contents of the new data received.
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to not accumulate all or portional of the data in
+ * the internal buffer, the consumedLength shall be set to the value of
+ * the dataLength or less. By default the consumedLength value is assumed 0.
+ * The data and dataLength reflect the non-consumed data and will be
+ * accumulated if consumedLength is not set.
+ *
+ * In comparison with onStreamComplete(), the data buffer cannot be
+ * adopted if this method returns NS_SUCCESS_ADOPTED_DATA.
+ */
+ void onIncrementalData(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in unsigned long dataLength,
+ [const,array,size_is(dataLength)] in octet data,
+ inout unsigned long consumedLength);
+
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIIncrementalStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)]
+interface nsIIncrementalStreamLoader : nsIStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aObserver
+ * An observer that will be notified when the data is complete.
+ */
+ void init(in nsIIncrementalStreamLoaderObserver aObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl
new file mode 100644
index 000000000..3af16ed62
--- /dev/null
+++ b/netwerk/base/nsIInputStreamChannel.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIURI;
+
+/**
+ * nsIInputStreamChannel
+ *
+ * This interface provides methods to initialize an input stream channel.
+ * The input stream channel serves as a data pump for an input stream.
+ */
+[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)]
+interface nsIInputStreamChannel : nsISupports
+{
+ /**
+ * Sets the URI for this channel. This must be called before the
+ * channel is opened, and it may only be called once.
+ */
+ void setURI(in nsIURI aURI);
+
+ /**
+ * Get/set the content stream
+ *
+ * This stream contains the data that will be pushed to the channel's
+ * stream listener. If the stream is non-blocking and supports the
+ * nsIAsyncInputStream interface, then the stream will be read directly.
+ * Otherwise, the stream will be read on a background thread.
+ *
+ * This attribute must be set before the channel is opened, and it may
+ * only be set once.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel
+ * has been opened.
+ */
+ attribute nsIInputStream contentStream;
+
+ /**
+ * Get/set the srcdoc data string. When the input stream channel is
+ * created to load a srcdoc iframe, this is set to hold the value of the
+ * srcdoc attribute.
+ *
+ * This should be the same value used to create contentStream, but this is
+ * not checked.
+ *
+ * Changing the value of this attribute will not otherwise affect the
+ * functionality of the channel or input stream.
+ */
+ attribute AString srcdocData;
+
+ /**
+ * Returns true if srcdocData has been set within the channel.
+ */
+ readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * The base URI to be used for the channel. Used when the base URI cannot
+ * be inferred by other means, for example when this is a srcdoc channel.
+ */
+ attribute nsIURI baseURI;
+};
diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl
new file mode 100644
index 000000000..83c29cdbb
--- /dev/null
+++ b/netwerk/base/nsIInputStreamPump.idl
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIStreamListener;
+
+/**
+ * nsIInputStreamPump
+ *
+ * This interface provides a means to configure and use a input stream pump
+ * instance. The input stream pump will asynchronously read from an input
+ * stream, and push data to an nsIStreamListener instance. It utilizes the
+ * current thread's nsIEventTarget in order to make reading from the stream
+ * asynchronous. A different thread can be used if the pump also implements
+ * nsIThreadRetargetableRequest.
+ *
+ * If the given stream supports nsIAsyncInputStream, then the stream pump will
+ * call the stream's AsyncWait method to drive the stream listener. Otherwise,
+ * the stream will be read on a background thread utilizing the stream
+ * transport service. More details are provided below.
+ */
+[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)]
+interface nsIInputStreamPump : nsIRequest
+{
+ /**
+ * Initialize the input stream pump.
+ *
+ * @param aStream
+ * contains the data to be read. if the input stream is non-blocking,
+ * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds
+ * then the stream will be read directly. otherwise, it will be read
+ * on a background thread using the stream transport service.
+ * @param aStreamPos
+ * specifies the stream offset from which to start reading. the
+ * offset value is absolute. pass -1 to specify the current offset.
+ * NOTE: this parameter is ignored if the underlying stream does not
+ * implement nsISeekableStream.
+ * @param aStreamLen
+ * specifies how much data to read from the stream. pass -1 to read
+ * all data available in the stream.
+ * @param aSegmentSize
+ * if the stream transport service is used, then this parameter
+ * specifies the segment size for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aSegmentCount
+ * if the stream transport service is used, then this parameter
+ * specifies the segment count for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aCloseWhenDone
+ * if true, the input stream will be closed after it has been read.
+ */
+ void init(in nsIInputStream aStream,
+ in long long aStreamPos,
+ in long long aStreamLen,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount,
+ in boolean aCloseWhenDone);
+
+ /**
+ * asyncRead causes the input stream to be read in chunks and delivered
+ * asynchronously to the listener via OnDataAvailable.
+ *
+ * @param aListener
+ * receives notifications.
+ * @param aListenerContext
+ * passed to listener methods.
+ */
+ void asyncRead(in nsIStreamListener aListener,
+ in nsISupports aListenerContext);
+};
diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl
new file mode 100644
index 000000000..b344a1544
--- /dev/null
+++ b/netwerk/base/nsILoadContextInfo.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+native OriginAttributesNativePtr(const mozilla::NeckoOriginAttributes*);
+
+interface nsILoadContext;
+interface nsIDOMWindow;
+
+/**
+ * Helper interface to carry informatin about the load context
+ * encapsulating origin attributes and IsAnonymous, IsPrivite properties.
+ * It shall be used where nsILoadContext cannot be used or is not
+ * available.
+ */
+
+[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)]
+interface nsILoadContextInfo : nsISupports
+{
+ const unsigned long NO_APP_ID = 0;
+ const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
+
+ /**
+ * Whether the context is in a Private Browsing mode
+ */
+ readonly attribute boolean isPrivate;
+
+ /**
+ * Whether the load is initiated as anonymous
+ */
+ readonly attribute boolean isAnonymous;
+
+ /**
+ * NeckoOriginAttributes hiding all the security context attributes
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)]
+ OriginAttributesNativePtr binaryOriginAttributesPtr();
+
+%{C++
+ /**
+ * De-XPCOMed getters
+ */
+ bool IsPrivate()
+ {
+ bool pb;
+ GetIsPrivate(&pb);
+ return pb;
+ }
+
+ bool IsAnonymous()
+ {
+ bool anon;
+ GetIsAnonymous(&anon);
+ return anon;
+ }
+
+ bool Equals(nsILoadContextInfo *aOther)
+ {
+ return IsAnonymous() == aOther->IsAnonymous() &&
+ *OriginAttributesPtr() == *aOther->OriginAttributesPtr();
+ }
+%}
+};
+
+/**
+ * Since NeckoOriginAttributes struct limits the implementation of
+ * nsILoadContextInfo (that needs to be thread safe) to C++,
+ * we need a scriptable factory to create instances of that
+ * interface from JS.
+ */
+[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)]
+interface nsILoadContextInfoFactory : nsISupports
+{
+ readonly attribute nsILoadContextInfo default;
+ readonly attribute nsILoadContextInfo private;
+ readonly attribute nsILoadContextInfo anonymous;
+ [implicit_jscontext]
+ nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes);
+ nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous);
+ nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous);
+};
diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl
new file mode 100644
index 000000000..4f89bd0e3
--- /dev/null
+++ b/netwerk/base/nsILoadGroup.idl
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsISimpleEnumerator;
+interface nsIRequestObserver;
+interface nsIInterfaceRequestor;
+interface nsIRequestContext;
+
+/**
+ * A load group maintains a collection of nsIRequest objects.
+ * This is used in lots of places where groups of requests need to be tracked.
+ * For example, nsIDocument::mDocumentLoadGroup is used to track all requests
+ * made for subdocuments in order to track page load progress and allow all
+ * requests made on behalf of the document to be stopped, etc.
+ */
+[scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)]
+interface nsILoadGroup : nsIRequest
+{
+ /**
+ * The group observer is notified when requests are added to and removed
+ * from this load group. The groupObserver is weak referenced.
+ */
+ attribute nsIRequestObserver groupObserver;
+
+ /**
+ * Accesses the default load request for the group. Each time a number
+ * of requests are added to a group, the defaultLoadRequest may be set
+ * to indicate that all of the requests are related to a base request.
+ *
+ * The load group inherits its load flags from the default load request.
+ * If the default load request is NULL, then the group's load flags are
+ * not changed.
+ */
+ attribute nsIRequest defaultLoadRequest;
+
+ /**
+ * Adds a new request to the group. This will cause the default load
+ * flags to be applied to the request. If this is a foreground
+ * request then the groupObserver's onStartRequest will be called.
+ *
+ * If the request is the default load request or if the default load
+ * request is null, then the load group will inherit its load flags from
+ * the request.
+ */
+ void addRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Removes a request from the group. If this is a foreground request
+ * then the groupObserver's onStopRequest will be called.
+ *
+ * By the time this call ends, aRequest will have been removed from the
+ * loadgroup, even if this function throws an exception.
+ */
+ void removeRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus);
+
+ /**
+ * Returns the requests contained directly in this group.
+ * Enumerator element type: nsIRequest.
+ */
+ readonly attribute nsISimpleEnumerator requests;
+
+ /**
+ * Returns the count of "active" requests (ie. requests without the
+ * LOAD_BACKGROUND bit set).
+ */
+ readonly attribute unsigned long activeCount;
+
+ /**
+ * Notification callbacks for the load group.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Context for managing things like js/css connection blocking,
+ * and per-tab connection grouping.
+ */
+ [noscript] readonly attribute nsID requestContextID;
+
+ /**
+ * The set of load flags that will be added to all new requests added to
+ * this group. Any existing requests in the load group are not modified,
+ * so it is expected these flags will be added before requests are added
+ * to the group - typically via nsIDocShell::defaultLoadFlags on a new
+ * docShell.
+ * Note that these flags are *not* added to the default request for the
+ * load group; it is expected the default request will already have these
+ * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before
+ * the docShell has created the default request.)
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /**
+ * The cached user agent override created by UserAgentOverrides.jsm. Used
+ * for all sub-resource requests in the loadgroup.
+ */
+ attribute ACString userAgentOverrideCache;
+};
diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl
new file mode 100644
index 000000000..3ec60a1a2
--- /dev/null
+++ b/netwerk/base/nsILoadGroupChild.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+/**
+ * nsILoadGroupChild provides a hierarchy of load groups so that the
+ * root load group can be used to conceptually tie a series of loading
+ * operations into a logical whole while still leaving them separate
+ * for the purposes of cancellation and status events.
+ */
+
+[scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)]
+interface nsILoadGroupChild : nsISupports
+{
+ /**
+ * The parent of this load group. It is stored with
+ * a nsIWeakReference/nsWeakPtr so there is no requirement for the
+ * parentLoadGroup to out live the child, nor will the child keep a
+ * reference count on the parent.
+ */
+ attribute nsILoadGroup parentLoadGroup;
+
+ /**
+ * The nsILoadGroup associated with this nsILoadGroupChild
+ */
+ readonly attribute nsILoadGroup childLoadGroup;
+
+ /**
+ * The rootLoadGroup is the recursive parent of this
+ * load group where parent is defined as parentlLoadGroup if set
+ * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes
+ * precedence.) The nsILoadGroup child is the root if neither parent
+ * nor loadgroup attribute is specified.
+ */
+ readonly attribute nsILoadGroup rootLoadGroup;
+};
+
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
new file mode 100644
index 000000000..78433c8b8
--- /dev/null
+++ b/netwerk/base/nsILoadInfo.idl
@@ -0,0 +1,732 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIDOMDocument;
+interface nsINode;
+interface nsIPrincipal;
+
+%{C++
+#include "nsTArray.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LoadTainting.h"
+
+class nsCString;
+%}
+
+[ref] native const_nsIPrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+[ref] native StringArrayRef(const nsTArray<nsCString>);
+
+typedef unsigned long nsSecurityFlags;
+
+/**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)]
+interface nsILoadInfo : nsISupports
+{
+ /**
+ * *** DEPRECATED ***
+ * No LoadInfo created within Gecko should contain this security flag.
+ * Please use any of the five security flags defined underneath.
+ * We only keep this security flag to provide backwards compatibilty.
+ */
+ const unsigned long SEC_NORMAL = 0;
+
+ /**
+ * The following five flags determine the security mode and hence what kind of
+ * security checks should be performed throughout the lifetime of the channel.
+ *
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ * * SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
+ * * SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+ * * SEC_REQUIRE_CORS_DATA_INHERITS
+ *
+ * Exactly one of these flags are required to be set in order to allow
+ * the channel to perform the correct security checks (SOP, CORS, ...) and
+ * return the correct result principal. If none or more than one of these
+ * flags are set AsyncOpen2 will fail.
+ */
+
+ /*
+ * Enforce the same origin policy where data: loads inherit
+ * the principal.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS = (1<<0);
+
+ /*
+ * Enforce the same origin policy but data: loads are blocked.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1);
+
+ /**
+ * Allow loads from other origins. Loads from data: will inherit
+ * the principal of the origin that triggered the load.
+ * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS = (1<<2);
+
+ /**
+ * Allow loads from other origins. Loads from data: will be allowed,
+ * but the resulting resource will get a null principal.
+ * Used in blink/webkit for <iframe>s. Likely also the mode
+ * that should be used by most Chrome code.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL = (1<<3);
+
+ /**
+ * Allow loads from any origin, but require CORS for cross-origin
+ * loads. Loads from data: are allowed and the result will inherit
+ * the principal of the origin that triggered the load.
+ * Commonly used by <img crossorigin>, <video crossorigin>,
+ * XHR, fetch(), etc.
+ */
+ const unsigned long SEC_REQUIRE_CORS_DATA_INHERITS = (1<<4);
+
+ /**
+ * Choose cookie policy. The default policy is equivalent to "INCLUDE" for
+ * SEC_REQUIRE_SAME_ORIGIN_* and SEC_ALLOW_CROSS_ORIGIN_* modes, and
+ * equivalent to "SAME_ORIGIN" for SEC_REQUIRE_CORS_DATA_INHERITS mode.
+ *
+ * This means that if you want to perform a CORS load with credentials, pass
+ * SEC_COOKIES_INCLUDE.
+ *
+ * Note that these flags are still subject to the user's cookie policies.
+ * For example, if the user is blocking 3rd party cookies, those cookies
+ * will be blocked no matter which of these flags are set.
+ */
+ const unsigned long SEC_COOKIES_DEFAULT = (0 << 5);
+ const unsigned long SEC_COOKIES_INCLUDE = (1 << 5);
+ const unsigned long SEC_COOKIES_SAME_ORIGIN = (2 << 5);
+ const unsigned long SEC_COOKIES_OMIT = (3 << 5);
+
+ /**
+ * Force inheriting of the Principal. The resulting resource will use the
+ * principal of the document which is doing the load. Setting this flag
+ * will cause GetChannelResultPrincipal to return the same principal as
+ * the loading principal that's passed in when creating the channel.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * So if the loading document comes from "http://a.com/", and the channel
+ * is loading the URI "http://b.com/whatever", GetChannelResultPrincipal
+ * will return a principal from "http://a.com/".
+ *
+ * This flag can not be used together with SEC_SANDBOXED. If both are passed
+ * to the LoadInfo constructor then this flag will be dropped. If you need
+ * to know whether this flag would have been present but was dropped due to
+ * sandboxing, check for the forceInheritPrincipalDropped flag.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL = (1<<7);
+
+ /**
+ * Sandbox the load. The resulting resource will use a freshly created
+ * null principal. So GetChannelResultPrincipal will always return a
+ * null principal whenever this flag is set.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * This flag can not be used together with SEC_FORCE_INHERIT_PRINCIPAL.
+ */
+ const unsigned long SEC_SANDBOXED = (1<<8);
+
+ /**
+ * Inherit the Principal for about:blank.
+ */
+ const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9);
+
+ /**
+ * Allow access to chrome: packages that are content accessible.
+ */
+ const unsigned long SEC_ALLOW_CHROME = (1<<10);
+
+ /**
+ * Disallow access to javascript: uris.
+ */
+ const unsigned long SEC_DISALLOW_SCRIPT = (1<<11);
+
+ /**
+ * Don't follow redirects. Instead the redirect response is returned
+ * as a successful response for the channel.
+ *
+ * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and
+ * REDIRECT_STS_UPGRADE, are still followed.
+ *
+ * Note: If this flag is set and the channel response is a redirect, then
+ * the response body might not be available.
+ * This can happen if the redirect was cached.
+ */
+ const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12);
+
+ /**
+ * Load an error page, it should be one of following : about:neterror,
+ * about:certerror, about:blocked, or about:tabcrashed.
+ */
+ const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13);
+
+ /**
+ * Force inheriting of the principalToInherit, overruling any owner
+ * that might be set on the channel. (Please note that channel.owner
+ * is deprecated and will be removed within Bug 1286838).
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principalToInherit set in the loadInfo.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER = (1<<14);
+
+ /**
+ * This is the principal of the network request's caller/requester where
+ * the resulting resource will be used. I.e. it is the principal which
+ * will get access to the result of the request. (Where "get access to"
+ * might simply mean "embed" depending on the type of resource that is
+ * loaded).
+ *
+ * For example for an image, it is the principal of the document where
+ * the image is rendered. For a stylesheet it is the principal of the
+ * document where the stylesheet will be applied.
+ *
+ * So if document at http://a.com/page.html loads an image from
+ * http://b.com/pic.jpg, then loadingPrincipal will be
+ * http://a.com/page.html.
+ *
+ * For <iframe> and <frame> loads, the LoadingPrincipal is the
+ * principal of the parent document. For top-level loads, the
+ * LoadingPrincipal is null. For all loads except top-level loads
+ * the LoadingPrincipal is never null.
+ *
+ * If the loadingPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the loadingPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * codebase-principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal loadingPrincipal;
+
+ /**
+ * A C++-friendly version of loadingPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingPrincipal)]
+ nsIPrincipal binaryLoadingPrincipal();
+
+ /**
+ * This is the principal which caused the network load to start. I.e.
+ * this is the principal which provided the URL to be loaded. This is
+ * often the same as the LoadingPrincipal, but there are a few cases
+ * where that's not true.
+ *
+ * For example for loads into an <iframe>, the LoadingPrincipal is always
+ * the principal of the parent document. However the triggeringPrincipal
+ * is the principal of the document which provided the URL that the
+ * <iframe> is navigating to. This could be the previous document inside
+ * the <iframe> which set document.location. Or a document elsewhere in
+ * the frame tree which contained a <a target="..."> which targetted the
+ * <iframe>.
+ *
+ * If a stylesheet links to a sub-resource, like an @imported stylesheet,
+ * or a background image, then the triggeringPrincipal is the principal
+ * of the stylesheet, while the LoadingPrincipal is the principal of the
+ * document being styled.
+ *
+ * The triggeringPrincipal is never null.
+ *
+ * If the triggeringPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the triggeringPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * codebase-principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ /**
+ * For non-document loads the principalToInherit is always null. For
+ * loads of type TYPE_DOCUMENT or TYPE_SUBDOCUMENT the principalToInherit
+ * might be null. If it's non null, then this is the principal that is
+ * inherited if a principal needs to be inherited. If the principalToInherit
+ * is null but the inherit flag is set, then the triggeringPrincipal is
+ * the principal that is inherited.
+ */
+ attribute nsIPrincipal principalToInherit;
+
+ /**
+ * A C++-friendly version of principalToInherit.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(PrincipalToInherit)]
+ nsIPrincipal binaryPrincipalToInherit();
+
+ /**
+ * This is the ownerDocument of the LoadingNode. Unless the LoadingNode
+ * is a Document, in which case the LoadingDocument is the same as the
+ * LoadingNode.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingDocument is null. When the LoadingDocument is not null, the
+ * LoadingPrincipal is set to the principal of the LoadingDocument.
+ */
+ readonly attribute nsIDOMDocument loadingDocument;
+
+ /**
+ * A C++-friendly version of loadingDocument (loadingNode).
+ * This is the Node where the resulting resource will be used. I.e. it is
+ * the Node which will get access to the result of the request. (Where
+ * "get access to" might simply mean "embed" depending on the type of
+ * resource that is loaded).
+ *
+ * For example for an <img>/<video> it is the image/video element. For
+ * document loads inside <iframe> and <frame>s, the LoadingNode is the
+ * <iframe>/<frame> element. For an XMLHttpRequest, it is the Document
+ * which contained the JS which initiated the XHR. For a stylesheet, it
+ * is the Document that contains <link rel=stylesheet>.
+ *
+ * For loads triggered by the HTML pre-parser, the LoadingNode is the
+ * Document which is currently being parsed.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingNode is null. If the LoadingNode is non-null, then the
+ * LoadingPrincipal is the principal of the LoadingNode.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingNode)]
+ nsINode binaryLoadingNode();
+
+ /**
+ * The securityFlags of that channel.
+ */
+ readonly attribute nsSecurityFlags securityFlags;
+
+%{ C++
+ inline nsSecurityFlags GetSecurityFlags()
+ {
+ nsSecurityFlags result;
+ mozilla::DebugOnly<nsresult> rv = GetSecurityFlags(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Allows to query only the security mode bits from above.
+ */
+ [infallible] readonly attribute unsigned long securityMode;
+
+ /**
+ * True if this request is embedded in a context that can't be third-party
+ * (i.e. an iframe embedded in a cross-origin parent window). If this is
+ * false, then this request may be third-party if it's a third-party to
+ * loadingPrincipal.
+ */
+ [infallible] readonly attribute boolean isInThirdPartyContext;
+
+ /**
+ * See the SEC_COOKIES_* flags above. This attribute will never return
+ * SEC_COOKIES_DEFAULT, but will instead return what the policy resolves to.
+ * I.e. SEC_COOKIES_SAME_ORIGIN for CORS mode, and SEC_COOKIES_INCLUDE
+ * otherwise.
+ */
+ [infallible] readonly attribute unsigned long cookiePolicy;
+
+ /**
+ * If forceInheritPrincipal is true, the data coming from the channel should
+ * use loadingPrincipal for its principal, even when the data is loaded over
+ * http:// or another protocol that would normally use a URI-based principal.
+ * This attribute will never be true when loadingSandboxed is true.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipal;
+
+ /**
+ * If forceInheritPrincipalOverruleOwner is true, the data coming from the
+ * channel should use principalToInherit for its principal, even when the
+ * data is loaded over http:// or another protocol that would normally use
+ * a URI-based principal.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalOverruleOwner;
+
+ /**
+ * If loadingSandboxed is true, the data coming from the channel is
+ * being loaded sandboxed, so it should have a nonce origin and
+ * hence should use a NullPrincipal.
+ */
+ [infallible] readonly attribute boolean loadingSandboxed;
+
+ /**
+ * If aboutBlankInherits is true, then about:blank should inherit
+ * the principal.
+ */
+ [infallible] readonly attribute boolean aboutBlankInherits;
+
+ /**
+ * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean allowChrome;
+
+ /**
+ * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean disallowScript;
+
+ /**
+ * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set.
+ */
+ [infallible] readonly attribute boolean dontFollowRedirects;
+
+ /**
+ * Returns true if SEC_LOAD_ERROR_PAGE is set.
+ */
+ [infallible] readonly attribute boolean loadErrorPage;
+
+ /**
+ * The external contentPolicyType of the channel, used for security checks
+ * like Mixed Content Blocking and Content Security Policy.
+ *
+ * Specifically, content policy types with _INTERNAL_ in their name will
+ * never get returned from this attribute.
+ */
+ readonly attribute nsContentPolicyType externalContentPolicyType;
+
+%{ C++
+ inline nsContentPolicyType GetExternalContentPolicyType()
+ {
+ nsContentPolicyType result;
+ mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * The internal contentPolicyType of the channel, used for constructing
+ * RequestContext values when creating a fetch event for an intercepted
+ * channel.
+ *
+ * This should not be used for the purposes of security checks, since
+ * the content policy implementations cannot be expected to deal with
+ * _INTERNAL_ values. Please use the contentPolicyType attribute above
+ * for that purpose.
+ */
+ [noscript, notxpcom]
+ nsContentPolicyType internalContentPolicyType();
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'upgrade-insecure-requests'. Used to identify upgrade
+ * requests in e10s where the loadingDocument is not available.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * upgradeInsecureRequests is false.
+ */
+ [infallible] readonly attribute boolean upgradeInsecureRequests;
+
+ /**
+ * If true, the content of the channel is queued up and checked
+ * if it matches a content signature. Note, setting this flag
+ * to true will negatively impact performance since the preloader
+ * can not start until all of the content is fetched from the
+ * netwerk.
+ *
+ * Only use that in combination with TYPE_DOCUMENT.
+ */
+ [infallible] attribute boolean verifySignedContent;
+
+ /**
+ * If true, this load will fail if it has no SRI integrity
+ */
+ [infallible] attribute boolean enforceSRI;
+
+ /**
+ * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
+ * object is created. Specifically, it will be dropped if the SEC_SANDBOXED
+ * flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL was
+ * dropped.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalDropped;
+
+ /**
+ * These are the window IDs of the window in which the element being
+ * loaded lives. parentOuterWindowID is the window ID of this window's
+ * parent.
+ *
+ * Note that these window IDs can be 0 if the window is not
+ * available. parentOuterWindowID will be the same as outerWindowID if the
+ * window has no parent.
+ */
+ [infallible] readonly attribute unsigned long long innerWindowID;
+ [infallible] readonly attribute unsigned long long outerWindowID;
+ [infallible] readonly attribute unsigned long long parentOuterWindowID;
+
+ /**
+ * Only when the element being loaded is <frame src="foo.html">
+ * (or, more generally, if the element QIs to nsIFrameLoaderOwner),
+ * the frameOuterWindowID is the outer window containing the
+ * foo.html document.
+ *
+ * Note: For other cases, frameOuterWindowID is 0.
+ */
+ [infallible] readonly attribute unsigned long long frameOuterWindowID;
+
+ /**
+ * For all loads of none TYPE_DOUCMENT this function resets the
+ * LoadingPrincipal, the TriggeringPrincipal and the
+ * PrincipalToInherit to a freshly created NullPrincipal which inherits
+ * the current origin attributes from the loadinfo.
+ * For loads of TYPE_DOCUMENT this function resets only the
+ * TriggeringPrincipal as well as the PrincipalToInherit to a freshly
+ * created NullPrincipal which inherits the origin attributes from
+ * the loadInfo. (Please note that the LoadingPrincipal for TYPE_DOCUMENT
+ * loads is always null.)
+ *
+ * WARNING: Please only use that function if you know exactly what
+ * you are doing!!!
+ */
+ void resetPrincipalsToNullPrincipal();
+
+ /**
+ * Customized NeckoOriginAttributes within LoadInfo to allow overwriting of the
+ * default originAttributes from the loadingPrincipal.
+ *
+ * In chrome side, originAttributes.privateBrowsingId will always be 0 even if
+ * the usePrivateBrowsing is true, because chrome docshell won't set
+ * privateBrowsingId on origin attributes (See bug 1278664). This is to make
+ * sure nsILoadInfo and nsILoadContext have the same origin attributes.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ NeckoOriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+%{ C++
+ inline mozilla::NeckoOriginAttributes GetOriginAttributes()
+ {
+ mozilla::NeckoOriginAttributes result;
+ mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Whenever a channel is openend by asyncOpen2() [or also open2()],
+ * lets set this flag so that redirects of such channels are also
+ * openend using asyncOpen2() [open2()].
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discareded.
+ *
+ */
+ [infallible] attribute boolean enforceSecurity;
+
+ /**
+ * Whenever a channel is evaluated by the ContentSecurityManager
+ * the first time, we set this flag to true to indicate that
+ * subsequent calls of AsyncOpen2() do not have to enforce all
+ * security checks again. E.g., after a redirect there is no
+ * need to set up CORS again. We need this separate flag
+ * because the redirectChain might also contain internal
+ * redirects which might pollute the redirectChain so we can't
+ * rely on the size of the redirectChain-array to query whether
+ * a channel got redirected or not.
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discarded.
+ *
+ */
+ [infallible] attribute boolean initialSecurityCheckDone;
+
+ /**
+ * Whenever a channel gets redirected, append the principal of the
+ * channel [before the channels got redirected] to the loadinfo,
+ * so that at every point this array lets us reason about all the
+ * redirects this channel went through.
+ * @param aPrincipal, the channelURIPrincipal before the channel
+ * got redirected.
+ * @param aIsInternalRedirect should be true if the channel is going
+ * through an internal redirect, otherwise false.
+ */
+ void appendRedirectedPrincipal(in nsIPrincipal principal,
+ in boolean isInternalRedirect);
+
+ /**
+ * An array of nsIPrincipals which stores redirects associated with this
+ * channel. This array is filled whether or not the channel has ever been
+ * opened. The last element of the array is associated with the most recent
+ * redirect. Please note, that this array *includes* internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChainIncludingInternalRedirects;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChainIncludingInternalRedirects)]
+ const_nsIPrincipalArray binaryRedirectChainIncludingInternalRedirects();
+
+ /**
+ * Same as RedirectChain but does *not* include internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChain;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ const_nsIPrincipalArray binaryRedirectChain();
+
+ /**
+ * Sets the list of unsafe headers according to CORS spec, as well as
+ * potentially forces a preflight.
+ * Note that you do not need to set the Content-Type header. That will be
+ * automatically detected as needed.
+ *
+ * Only call this function when using the SEC_REQUIRE_CORS_DATA_INHERITS mode.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightInfo(in StringArrayRef unsafeHeaders,
+ in boolean forcePreflight);
+
+ /**
+ * A C++-friendly getter for the list of cors-unsafe headers.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(CorsUnsafeHeaders)]
+ StringArrayRef corsUnsafeHeaders();
+
+ /**
+ * Returns value set through setCorsPreflightInfo.
+ */
+ [infallible] readonly attribute boolean forcePreflight;
+
+ /**
+ * A C++ friendly getter for the forcePreflight flag.
+ */
+ [infallible] readonly attribute boolean isPreflight;
+
+ /**
+ * When this request would be mixed-content and we do not have an
+ * entry in the HSTS cache, we send an HSTS priming request to
+ * determine if it is ok to upgrade the request to HTTPS.
+ */
+ /**
+ * True if this is a mixed-content load and HSTS priming request will be sent.
+ */
+ [noscript, infallible] readonly attribute boolean forceHSTSPriming;
+ /**
+ * Carry the decision whether this load would be blocked by mixed content so
+ * that if HSTS priming fails, the correct decision can be made.
+ */
+ [noscript, infallible] readonly attribute boolean mixedContentWouldBlock;
+
+ /**
+ * Mark this LoadInfo as needing HSTS Priming
+ *
+ * @param wouldBlock Carry the decision of Mixed Content Blocking to be
+ * applied when HSTS priming is complete.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setHSTSPriming(in boolean mixeContentWouldBlock);
+ [noscript, notxpcom, nostdcall]
+ void clearHSTSPriming();
+
+ /**
+ * Constants reflecting the channel tainting. These are mainly defined here
+ * for script. Internal C++ code should use the enum defined in LoadTainting.h.
+ * See LoadTainting.h for documentation.
+ */
+ const unsigned long TAINTING_BASIC = 0;
+ const unsigned long TAINTING_CORS = 1;
+ const unsigned long TAINTING_OPAQUE = 2;
+
+ /**
+ * Determine the associated channel's current tainting. Note, this can
+ * change due to a service worker intercept, so it should be checked after
+ * OnStartRequest() fires.
+ */
+ readonly attribute unsigned long tainting;
+
+ /**
+ * Note a new tainting level and possibly increase the current tainting
+ * to match. If the tainting level is already greater than the given
+ * value, then there is no effect. It is not possible to reduce the tainting
+ * level on an existing channel/loadinfo.
+ */
+ void maybeIncreaseTainting(in unsigned long aTainting);
+
+ /**
+ * Various helper code to provide more convenient C++ access to the tainting
+ * attribute and maybeIncreaseTainting().
+ */
+%{C++
+ static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic),
+ "basic tainting enums should match");
+ static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS),
+ "cors tainting enums should match");
+ static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque),
+ "opaque tainting enums should match");
+
+ mozilla::LoadTainting GetTainting()
+ {
+ uint32_t tainting = TAINTING_BASIC;
+ MOZ_ALWAYS_SUCCEEDS(GetTainting(&tainting));
+ return static_cast<mozilla::LoadTainting>(tainting);
+ }
+
+ void MaybeIncreaseTainting(mozilla::LoadTainting aTainting)
+ {
+ uint32_t tainting = static_cast<uint32_t>(aTainting);
+ MOZ_ALWAYS_SUCCEEDS(MaybeIncreaseTainting(tainting));
+ }
+%}
+
+ /**
+ * Returns true if this load is for top level document.
+ * Note that the load for a sub-frame's document will return false here.
+ */
+ [infallible] readonly attribute boolean isTopLevelLoad;
+};
diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl
new file mode 100644
index 000000000..82992d939
--- /dev/null
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+[scriptable, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
+interface nsIMIMEInputStream : nsIInputStream
+{
+ /**
+ * When true a "Content-Length" header is automatically added to the
+ * stream. The value of the content-length is automatically calculated
+ * using the available() method on the data stream. The value is
+ * recalculated every time the stream is rewinded to the start.
+ * Not allowed to be changed once the stream has been started to be read.
+ */
+ attribute boolean addContentLength;
+
+ /**
+ * Adds an additional header to the stream on the form "name: value". May
+ * not be called once the stream has been started to be read.
+ * @param name name of the header
+ * @param value value of the header
+ */
+ void addHeader(in string name, in string value);
+
+ /**
+ * Sets data-stream. May not be called once the stream has been started
+ * to be read.
+ * The cursor of the new stream should be located at the beginning of the
+ * stream if the implementation of the nsIMIMEInputStream also is used as
+ * an nsISeekableStream.
+ * @param stream stream containing the data for the stream
+ */
+ void setData(in nsIInputStream stream);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
diff --git a/netwerk/base/nsIMultiPartChannel.idl b/netwerk/base/nsIMultiPartChannel.idl
new file mode 100644
index 000000000..1b9517628
--- /dev/null
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An interface to access the the base channel
+ * associated with a MultiPartChannel.
+ */
+
+[scriptable, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
+interface nsIMultiPartChannel : nsISupports
+{
+ /**
+ * readonly attribute to access the underlying channel
+ */
+ readonly attribute nsIChannel baseChannel;
+
+ /**
+ * Attribute guaranteed to be different for different parts of
+ * the same multipart document.
+ */
+ readonly attribute uint32_t partID;
+
+ /**
+ * Set to true when onStopRequest is received from the base channel.
+ * The listener can check this from its onStopRequest to determine
+ * whether more data can be expected.
+ */
+ readonly attribute boolean isLastPart;
+};
diff --git a/netwerk/base/nsINSSErrorsService.idl b/netwerk/base/nsINSSErrorsService.idl
new file mode 100644
index 000000000..95a6e6d0c
--- /dev/null
+++ b/netwerk/base/nsINSSErrorsService.idl
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(12f60021-e14b-4020-99d1-ed2c795be66a)]
+interface nsINSSErrorsService : nsISupports
+{
+ /**
+ * @param aNSPRCode An error code obtained using PR_GetError()
+ * @return True if it is error code defined by the NSS library
+ */
+ boolean isNSSErrorCode(in int32_t aNSPRCode);
+
+ /**
+ * Function will fail if aNSPRCode is not an NSS error code.
+ * @param aNSPRCode An error code obtained using PR_GetError()
+ * @return The result of the conversion, an XPCOM error code
+ */
+ nsresult getXPCOMFromNSSError(in int32_t aNSPRCode);
+
+ /**
+ * Function will fail if aXPCOMErrorCode is not an NSS error code.
+ * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError
+ * return A localized human readable error explanation.
+ */
+ AString getErrorMessage(in nsresult aXPCOMErrorCode);
+
+ /**
+ * Function will fail if aXPCOMErrorCode is not an NSS error code.
+ * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError
+ * return the error class of the code, either ERROR_CLASS_BAD_CERT
+ * or ERROR_CLASS_SSL_PROTOCOL
+ */
+ uint32_t getErrorClass(in nsresult aXPCOMErrorCode);
+
+ const unsigned long ERROR_CLASS_SSL_PROTOCOL = 1;
+ const unsigned long ERROR_CLASS_BAD_CERT = 2;
+
+ /**
+ * The following values define the range of NSPR error codes used by NSS.
+ * NSS remains the authorative source for these numbers, as a result,
+ * the values might change in the future.
+ * The security module will perform a runtime check and assertion
+ * to ensure the values are in synch with NSS.
+ */
+ const long NSS_SEC_ERROR_BASE = -(0x2000);
+ const long NSS_SEC_ERROR_LIMIT = (NSS_SEC_ERROR_BASE + 1000);
+ const long NSS_SSL_ERROR_BASE = -(0x3000);
+ const long NSS_SSL_ERROR_LIMIT = (NSS_SSL_ERROR_BASE + 1000);
+
+ /**
+ * The error codes within each module must fit in 16 bits. We want these
+ * errors to fit in the same module as the NSS errors but not overlap with
+ * any of them. Converting an NSS SEC, NSS SSL, or mozilla::pkix error to
+ * an NS error involves negating the value of the error and then
+ * synthesizing an error in the NS_ERROR_MODULE_SECURITY module. Hence,
+ * mozilla::pkix errors will start at a negative value that both doesn't
+ * overlap with the current value ranges for NSS errors and that will fit
+ * in 16 bits when negated.
+ *
+ * Keep these in sync with pkixnss.h.
+ */
+ const long MOZILLA_PKIX_ERROR_BASE = -(0x4000);
+ const long MOZILLA_PKIX_ERROR_LIMIT = (MOZILLA_PKIX_ERROR_BASE + 1000);
+};
diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl
new file mode 100644
index 000000000..48cd9b36a
--- /dev/null
+++ b/netwerk/base/nsINestedURI.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsINestedURI is an interface that must be implemented by any nsIURI
+ * implementation which has an "inner" URI that it actually gets data
+ * from.
+ *
+ * For example, if URIs for the scheme "sanitize" have the structure:
+ *
+ * sanitize:http://example.com
+ *
+ * and opening a channel on such a sanitize: URI gets the data from
+ * http://example.com, sanitizes it, and returns it, then the sanitize: URI
+ * should implement nsINestedURI and return the http://example.com URI as its
+ * inner URI.
+ */
+[scriptable, uuid(6de2c874-796c-46bf-b57f-0d7bd7d6cab0)]
+interface nsINestedURI : nsISupports
+{
+ /**
+ * The inner URI for this nested URI. This must not return null if the
+ * getter succeeds; URIs that have no inner must not QI to this interface.
+ * Dynamically changing whether there is an inner URI is not allowed.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innerURI;
+
+ /**
+ * The innermost URI for this nested URI. This must not return null if the
+ * getter succeeds. This is equivalent to repeatedly calling innerURI while
+ * the returned URI QIs to nsINestedURI.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innermostURI;
+};
diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl
new file mode 100644
index 000000000..c7388354d
--- /dev/null
+++ b/netwerk/base/nsINetAddr.idl
@@ -0,0 +1,88 @@
+/* vim: et ts=4 sw=4 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * nsINetAddr
+ *
+ * This interface represents a native NetAddr struct in a readonly
+ * interface.
+ */
+[scriptable, uuid(652B9EC5-D159-45D7-9127-50BB559486CD)]
+interface nsINetAddr : nsISupports
+{
+ /**
+ * @return the address family of the network address, which corresponds to
+ * one of the FAMILY_ constants.
+ */
+ readonly attribute unsigned short family;
+
+ /**
+ * @return Either the IP address (FAMILY_INET, FAMILY_INET6) or the path
+ * (FAMILY_LOCAL) in string form. IP addresses are in the format produced by
+ * mozilla::net::NetAddrToString.
+ *
+ * Note: Paths for FAMILY_LOCAL may have length limitations which are
+ * implementation dependent and not documented as part of this interface.
+ */
+ readonly attribute AUTF8String address;
+
+ /**
+ * @return the port number for a FAMILY_INET or FAMILY_INET6 address.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET or
+ * FAMILY_INET6.
+ */
+ readonly attribute unsigned short port;
+
+ /**
+ * @return the flow label for a FAMILY_INET6 address.
+ *
+ * @see http://www.ietf.org/rfc/rfc3697.txt
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long flow;
+
+ /**
+ * @return the address scope of a FAMILY_INET6 address.
+ *
+ * @see http://tools.ietf.org/html/rfc4007
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long scope;
+
+ /**
+ * @return whether a FAMILY_INET6 address is mapped from FAMILY_INET.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute boolean isV4Mapped;
+
+ /**
+ * Network address families. These correspond to all the network address
+ * families supported by the NetAddr struct.
+ */
+ const unsigned long FAMILY_INET = 1;
+ const unsigned long FAMILY_INET6 = 2;
+ const unsigned long FAMILY_LOCAL = 3;
+
+ /**
+ * @return the underlying NetAddr struct.
+ */
+ [noscript] NetAddr getNetAddr();
+};
diff --git a/netwerk/base/nsINetUtil.idl b/netwerk/base/nsINetUtil.idl
new file mode 100644
index 000000000..800a9ae90
--- /dev/null
+++ b/netwerk/base/nsINetUtil.idl
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrefBranch;
+
+/**
+ * nsINetUtil provides various network-related utility methods.
+ */
+[scriptable, uuid(fe2625ec-b884-4df1-b39c-9e830e47aa94)]
+interface nsINetUtil : nsISupports
+{
+ /**
+ * Parse a Content-Type header value in strict mode. This is a more
+ * conservative parser that reject things that violate RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * that are used for HTTP requests, and those that are used to make security
+ * decisions.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseRequestContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Parse a Content-Type header value in relaxed mode. This is a more
+ * permissive parser that ignores things that go against RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * received from web servers where we want to make a best effort attempt
+ * at extracting a useful MIME type and charset.
+ *
+ * NOTE: DO NOT USE THIS if you're going to make security decisions
+ * based on the result.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseResponseContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Test whether the given URI's handler has the given protocol flags.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether the protocol handler for aURI has all the flags
+ * in aFlags.
+ */
+ boolean protocolHasFlags(in nsIURI aURI, in unsigned long aFlag);
+
+ /**
+ * Test whether the protocol handler for this URI or that for any of
+ * its inner URIs has the given protocol flags. This will QI aURI to
+ * nsINestedURI and walk the nested URI chain.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether any of the protocol handlers involved have all the flags
+ * in aFlags.
+ */
+ boolean URIChainHasFlags(in nsIURI aURI, in unsigned long aFlags);
+
+ /**
+ * Take aURI and produce an immutable version of it for the caller. If aURI
+ * is immutable this will be aURI itself; otherwise this will be a clone,
+ * marked immutable if possible. Passing null to this method is allowed; in
+ * that case it will return null.
+ */
+ nsIURI toImmutableURI(in nsIURI aURI);
+
+ /**
+ * Create a simple nested URI using the result of
+ * toImmutableURI on the passed-in aURI which may not be null.
+ * Note: The return URI will not have had its spec set yet.
+ */
+ nsIURI newSimpleNestedURI(in nsIURI aURI);
+
+ /** Escape every character with its %XX-escaped equivalent */
+ const unsigned long ESCAPE_ALL = 0;
+
+ /** Leave alphanumeric characters intact and %XX-escape all others */
+ const unsigned long ESCAPE_XALPHAS = 1;
+
+ /** Leave alphanumeric characters intact, convert spaces to '+',
+ %XX-escape all others */
+ const unsigned long ESCAPE_XPALPHAS = 2;
+
+ /** Leave alphanumeric characters and forward slashes intact,
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_PATH = 4;
+
+ /**
+ * escape a string with %00-style escaping
+ */
+ ACString escapeString(in ACString aString, in unsigned long aEscapeType);
+
+ /** %XX-escape URL scheme */
+ const unsigned long ESCAPE_URL_SCHEME = 1;
+
+ /** %XX-escape username in the URL */
+ const unsigned long ESCAPE_URL_USERNAME = 1 << 1;
+
+ /** %XX-escape password in the URL */
+ const unsigned long ESCAPE_URL_PASSWORD = 1 << 2;
+
+ /** %XX-escape URL host */
+ const unsigned long ESCAPE_URL_HOST = 1 << 3;
+
+ /** %XX-escape URL directory */
+ const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4;
+
+ /** %XX-escape file basename in the URL */
+ const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5;
+
+ /** %XX-escape file extension in the URL */
+ const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6;
+
+ /** %XX-escape URL parameters */
+ const unsigned long ESCAPE_URL_PARAM = 1 << 7;
+
+ /** %XX-escape URL query */
+ const unsigned long ESCAPE_URL_QUERY = 1 << 8;
+
+ /** %XX-escape URL ref */
+ const unsigned long ESCAPE_URL_REF = 1 << 9;
+
+ /** %XX-escape URL path - same as escaping directory, basename and extension */
+ const unsigned long ESCAPE_URL_FILEPATH =
+ ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION;
+
+ /** %XX-escape scheme, username, password, host, path, params, query and ref */
+ const unsigned long ESCAPE_URL_MINIMAL =
+ ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD |
+ ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM |
+ ESCAPE_URL_QUERY | ESCAPE_URL_REF;
+
+ /** Force %XX-escaping of already escaped sequences */
+ const unsigned long ESCAPE_URL_FORCED = 1 << 10;
+
+ /** Skip non-ascii octets, %XX-escape all others */
+ const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11;
+
+ /**
+ * Skip graphic octets (0x20-0x7E) when escaping
+ * Skips all ASCII octets (0x00-0x7F) when unescaping
+ */
+ const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12;
+
+ /** Force %XX-escape of colon */
+ const unsigned long ESCAPE_URL_COLON = 1 << 14;
+
+ /** Skip C0 and DEL from unescaping */
+ const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15;
+
+ /**
+ * %XX-Escape invalid chars in a URL segment.
+ *
+ * @param aStr the URL to be escaped
+ * @param aFlags the URL segment type flags
+ *
+ * @return the escaped string (the string itself if escaping did not happen)
+ *
+ */
+ ACString escapeURL(in ACString aStr, in unsigned long aFlags);
+
+ /**
+ * Expands URL escape sequences
+ *
+ * @param aStr the URL to be unescaped
+ * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL
+ * are recognized. If |aFlags| is 0 all escape sequences are
+ * unescaped
+ * @return unescaped string
+ */
+ ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags);
+
+ /**
+ * Extract the charset parameter location and value from a content-type
+ * header.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aCharsetStart index of the start of the charset parameter
+ * (the ';' separating it from what came before) in aTypeHeader.
+ * If this function returns false, this argument will still be
+ * set, to the index of the location where a new charset should
+ * be inserted.
+ * @param [out] aCharsetEnd index of the end of the charset parameter (the
+ * ';' separating it from what comes after, or the end
+ * of the string) in aTypeHeader. If this function returns
+ * false, this argument will still be set, to the index of the
+ * location where a new charset should be inserted.
+ *
+ * @return whether a charset parameter was found. This can be false even in
+ * cases when parseContentType would claim to have a charset, if the type
+ * that won out does not have a charset parameter specified.
+ */
+ boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out long aCharsetStart,
+ out long aCharsetEnd);
+
+/**
+ * Parse an attribute referrer policy string (no-referrer, origin, unsafe-url)
+ * and return the according integer code (defined in nsIHttpChannel.idl)
+ *
+ * @param aPolicyString
+ * the policy string given as attribute
+ * @return aPolicyEnum
+ * referrer policy code from nsIHttpChannel.idl, (see parser in
+ * ReferrerPolicy.h for details)
+ */
+ unsigned long parseAttributePolicyString(in AString aPolicyString);
+};
diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl
new file mode 100644
index 000000000..bd8804508
--- /dev/null
+++ b/netwerk/base/nsINetworkInfoService.idl
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener for getting list of addresses.
+ */
+[scriptable, uuid(c4bdaac1-3ab1-4fdb-9a16-17cbed794603)]
+interface nsIListNetworkAddressesListener : nsISupports
+{
+ /**
+ * Callback function that gets called by nsINetworkInfoService.listNetworkAddresses.
+ * Each address in the array is a string IP address in canonical form,
+ * e.g. 192.168.1.10, or an IPV6 address in string form.
+ */
+ void onListedNetworkAddresses([array, size_is(aAddressArraySize)] in string aAddressArray,
+ in unsigned long aAddressArraySize);
+ void onListNetworkAddressesFailed();
+};
+
+/**
+ * Listener for getting hostname.
+ */
+[scriptable, uuid(3ebdcb62-2df4-4042-8864-3fa81abd4693)]
+interface nsIGetHostnameListener : nsISupports
+{
+ void onGotHostname(in AUTF8String aHostname);
+ void onGetHostnameFailed();
+};
+
+/**
+ * Service information
+ */
+[scriptable, uuid(55fc8dae-4a58-4e0f-a49b-901cbabae809)]
+interface nsINetworkInfoService : nsISupports
+{
+ /**
+ * Obtain a list of local machine network addresses. The listener object's
+ * onListedNetworkAddresses will be called with the obtained addresses.
+ * On failure, the listener object's onListNetworkAddressesFailed() will be called.
+ */
+ void listNetworkAddresses(in nsIListNetworkAddressesListener aListener);
+
+ /**
+ * Obtain the hostname of the local machine. The listener object's
+ * onGotHostname will be called with the obtained hostname.
+ * On failure, the listener object's onGetHostnameFailed() will be called.
+ */
+ void getHostname(in nsIGetHostnameListener aListener);
+};
+
+%{ C++
+#define NETWORKINFOSERVICE_CONTRACT_ID \
+ "@mozilla.org/network-info-service;1"
+%}
diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl
new file mode 100644
index 000000000..17d27de42
--- /dev/null
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicyBase.idl"
+
+interface nsIChannel;
+interface nsIConsoleReportCollector;
+interface nsIOutputStream;
+interface nsIURI;
+
+%{C++
+#include "nsIConsoleReportCollector.h"
+namespace mozilla {
+namespace dom {
+class ChannelInfo;
+}
+}
+%}
+
+[ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
+
+/**
+ * Interface to allow implementors of nsINetworkInterceptController to control the behaviour
+ * of intercepted channels without tying implementation details of the interception to
+ * the actual channel. nsIInterceptedChannel is expected to be implemented by objects
+ * which do not implement nsIChannel.
+ */
+
+[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)]
+interface nsIInterceptedChannel : nsISupports
+{
+ /**
+ * Instruct a channel that has been intercepted to continue with the original
+ * network request.
+ */
+ void resetInterception();
+
+ /**
+ * Set the status and reason for the forthcoming synthesized response.
+ * Multiple calls overwrite existing values.
+ */
+ void synthesizeStatus(in uint16_t status, in ACString reason);
+
+ /**
+ * Attach a header name/value pair to the forthcoming synthesized response.
+ * Overwrites any existing header value.
+ */
+ void synthesizeHeader(in ACString name, in ACString value);
+
+ /**
+ * Instruct a channel that has been intercepted that a response has been
+ * synthesized and can now be read. No further header modification is allowed
+ * after this point. The caller may optionally pass a spec for a URL that
+ * this response originates from; an empty string will cause the original
+ * intercepted request's URL to be used instead.
+ */
+ void finishSynthesizedResponse(in ACString finalURLSpec);
+
+ /**
+ * Cancel the pending intercepted request.
+ * @return NS_ERROR_FAILURE if the response has already been synthesized or
+ * the original request has been instructed to continue.
+ */
+ void cancel(in nsresult status);
+
+ /**
+ * The synthesized response body to be produced.
+ */
+ readonly attribute nsIOutputStream responseBody;
+
+ /**
+ * The underlying channel object that was intercepted.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The URL of the underlying channel object, corrected for a potential
+ * secure upgrade.
+ */
+ readonly attribute nsIURI secureUpgradedChannelURI;
+
+ /**
+ * This method allows to override the channel info for the channel.
+ */
+ [noscript]
+ void setChannelInfo(in ChannelInfo channelInfo);
+
+ /**
+ * Get the internal load type from the underlying channel.
+ */
+ [noscript]
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ [noscript]
+ readonly attribute nsIConsoleReportCollector consoleReportCollector;
+
+%{C++
+ already_AddRefed<nsIConsoleReportCollector>
+ GetConsoleReportCollector()
+ {
+ nsCOMPtr<nsIConsoleReportCollector> reporter;
+ GetConsoleReportCollector(getter_AddRefs(reporter));
+ return reporter.forget();
+ }
+%}
+
+ /**
+ * Allow the ServiceWorkerManager to set an RAII-style object on the
+ * intercepted channel that should be released once the channel is
+ * torn down.
+ */
+ [noscript]
+ void setReleaseHandle(in nsISupports aHandle);
+};
+
+/**
+ * Interface to allow consumers to attach themselves to a channel's
+ * notification callbacks/loadgroup and determine if a given channel
+ * request should be intercepted before any network request is initiated.
+ */
+
+[scriptable, uuid(70d2b4fe-a552-48cd-8d93-1d8437a56b53)]
+interface nsINetworkInterceptController : nsISupports
+{
+ /**
+ * Returns true if a channel should avoid initiating any network
+ * requests until specifically instructed to do so.
+ *
+ * @param aURI the URI being requested by a channel
+ * @param aIsNavigate True if the request is for a navigation, false for a fetch.
+ */
+ bool shouldPrepareForIntercept(in nsIURI aURI, in bool aIsNonSubresourceRequest);
+
+ /**
+ * Notification when a given intercepted channel is prepared to accept a synthesized
+ * response via the provided stream.
+ *
+ * @param aChannel the controlling interface for a channel that has been intercepted
+ */
+ void channelIntercepted(in nsIInterceptedChannel aChannel);
+};
diff --git a/netwerk/base/nsINetworkLinkService.idl b/netwerk/base/nsINetworkLinkService.idl
new file mode 100644
index 000000000..456c71b6c
--- /dev/null
+++ b/netwerk/base/nsINetworkLinkService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Network link status monitoring service.
+ */
+[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
+interface nsINetworkLinkService : nsISupports
+{
+ /* Link type constants */
+ const unsigned long LINK_TYPE_UNKNOWN = 0;
+ const unsigned long LINK_TYPE_ETHERNET = 1;
+ const unsigned long LINK_TYPE_USB = 2;
+ const unsigned long LINK_TYPE_WIFI = 3;
+ const unsigned long LINK_TYPE_WIMAX = 4;
+ const unsigned long LINK_TYPE_2G = 5;
+ const unsigned long LINK_TYPE_3G = 6;
+ const unsigned long LINK_TYPE_4G = 7;
+
+ /**
+ * This is set to true when the system is believed to have a usable
+ * network connection.
+ *
+ * The link is only up when network connections can be established. For
+ * example, the link is down during DHCP configuration (unless there
+ * is another usable interface already configured).
+ *
+ * If the link status is not currently known, we generally assume that
+ * it is up.
+ */
+ readonly attribute boolean isLinkUp;
+
+ /**
+ * This is set to true when we believe that isLinkUp is accurate.
+ */
+ readonly attribute boolean linkStatusKnown;
+
+ /**
+ * The type of network connection.
+ */
+ readonly attribute unsigned long linkType;
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TOPIC whenever one of isLinkUp or linkStatusKnown
+ * changes. We pass one of the NS_NETWORK_LINK_DATA_ constants below
+ * as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TOPIC "network:link-status-changed"
+
+/**
+ * isLinkUp is now true, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_UP "up"
+/**
+ * isLinkUp is now false, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_DOWN "down"
+/**
+ * isLinkUp is still true, but the network setup is modified.
+ * linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_CHANGED "changed"
+/**
+ * linkStatusKnown is now false.
+ */
+#define NS_NETWORK_LINK_DATA_UNKNOWN "unknown"
+
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TYPE_TOPIC whenever the network connection type
+ * changes. We pass one of the valid connection type constants
+ * below as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TYPE_TOPIC "network:link-type-changed"
+
+/** We were unable to determine the network connection type */
+#define NS_NETWORK_LINK_TYPE_UNKNOWN "unknown"
+
+/** A standard wired ethernet connection */
+#define NS_NETWORK_LINK_TYPE_ETHERNET "ethernet"
+
+/** A connection via a USB port */
+#define NS_NETWORK_LINK_TYPE_USB "usb"
+
+/** A connection via a WiFi access point (IEEE802.11) */
+#define NS_NETWORK_LINK_TYPE_WIFI "wifi"
+
+/** A connection via WiMax (IEEE802.16) */
+#define NS_NETWORK_LINK_TYPE_WIMAX "wimax"
+
+/** A '2G' mobile connection (e.g. GSM, GPRS, EDGE) */
+#define NS_NETWORK_LINK_TYPE_2G "2g"
+
+/** A '3G' mobile connection (e.g. UMTS, CDMA) */
+#define NS_NETWORK_LINK_TYPE_3G "3g"
+
+/** A '4G' mobile connection (e.g. LTE, UMB) */
+#define NS_NETWORK_LINK_TYPE_4G "4g"
+%}
diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl
new file mode 100644
index 000000000..1b6b9576b
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictor.idl
@@ -0,0 +1,165 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsILoadContext;
+interface nsINetworkPredictorVerifier;
+
+typedef unsigned long PredictorPredictReason;
+typedef unsigned long PredictorLearnReason;
+
+/**
+ * nsINetworkPredictor - learn about pages users visit, and allow us to take
+ * predictive actions upon future visits.
+ * NOTE: nsINetworkPredictor should only
+ * be used on the main thread.
+ */
+[scriptable, uuid(acc88e7c-3f39-42c7-ac31-6377c2c3d73e)]
+interface nsINetworkPredictor : nsISupports
+{
+ /**
+ * Prediction reasons
+ *
+ * PREDICT_LINK - we are being asked to take predictive action because
+ * the user is hovering over a link.
+ *
+ * PREDICT_LOAD - we are being asked to take predictive action because
+ * the user has initiated a pageload.
+ *
+ * PREDICT_STARTUP - we are being asked to take predictive action
+ * because the browser is starting up.
+ */
+ const PredictorPredictReason PREDICT_LINK = 0;
+ const PredictorPredictReason PREDICT_LOAD = 1;
+ const PredictorPredictReason PREDICT_STARTUP = 2;
+
+ /**
+ * Start taking predictive actions
+ *
+ * Calling this will cause the predictor to (possibly) start
+ * taking actions such as DNS prefetch and/or TCP preconnect based on
+ * (1) the host name that we are given, and (2) the reason we are being
+ * asked to take actions.
+ *
+ * @param targetURI - The URI we are being asked to take actions based on.
+ * @param sourceURI - The URI that is currently loaded. This is so we can
+ * avoid doing predictive actions for link hover on an HTTPS page (for
+ * example).
+ * @param reason - The reason we are being asked to take actions. Can be
+ * any of the PREDICT_* values above.
+ * In the case of PREDICT_LINK, targetURI should be the URI of the link
+ * that is being hovered over, and sourceURI should be the URI of the page
+ * on which the link appears.
+ * In the case of PREDICT_LOAD, targetURI should be the URI of the page that
+ * is being loaded and sourceURI should be null.
+ * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be
+ * null.
+ * @param loadContext - The nsILoadContext of the page load we are predicting
+ * about.
+ * @param verifier - An nsINetworkPredictorVerifier used in testing to ensure
+ * we're predicting the way we expect to. Not necessary (or desired) for
+ * normal operation.
+ */
+ void predict(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in nsILoadContext loadContext,
+ in nsINetworkPredictorVerifier verifier);
+
+
+ /*
+ * Reasons we are learning something
+ *
+ * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a
+ * pageload (NOTE: this should ONLY be used by tests)
+ *
+ * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload
+ *
+ * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI
+ *
+ * LEARN_STARTUP - we are learning about a page loaded during startup
+ */
+ const PredictorLearnReason LEARN_LOAD_TOPLEVEL = 0;
+ const PredictorLearnReason LEARN_LOAD_SUBRESOURCE = 1;
+ const PredictorLearnReason LEARN_LOAD_REDIRECT = 2;
+ const PredictorLearnReason LEARN_STARTUP = 3;
+
+ /**
+ * Add to our compendium of knowledge
+ *
+ * This adds to our prediction database to make things (hopefully)
+ * smarter next time we predict something.
+ *
+ * @param targetURI - The URI that was loaded that we are keeping track of.
+ * @param sourceURI - The URI that caused targetURI to be loaded (for page
+ * loads). This means the DOCUMENT URI.
+ * @param reason - The reason we are learning this bit of knowledge.
+ * Reason can be any of the LEARN_* values.
+ * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a
+ * subresource of a page, and sourceURI should be the top-level URI.
+ * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a
+ * top-level resource that was redirected to, and sourceURI is the
+ * ORIGINAL URI of said top-level resource.
+ * In the case of LEARN_STARTUP, targetURI should be the URI of a page
+ * that was loaded immediately after browser startup, and sourceURI should
+ * be null.
+ * @param loadContext - The nsILoadContext for the page load that we are
+ * learning about.
+ */
+ void learn(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in nsILoadContext loadContext);
+
+ /**
+ * Clear out all our learned knowledge
+ *
+ * This removes everything from our database so that any predictions begun
+ * after this completes will start from a blank slate.
+ */
+ void reset();
+};
+
+%{C++
+// Wrapper functions to make use of the predictor easier and less invasive
+class nsIChannel;
+class nsIDocument;
+class nsILoadContext;
+class nsILoadGroup;
+class nsINetworkPredictorVerifier;
+
+namespace mozilla {
+namespace net {
+
+nsresult PredictorPredict(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorPredictReason reason,
+ nsILoadContext *loadContext,
+ nsINetworkPredictorVerifier *verifier);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadContext *loadContext);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsIDocument *document);
+
+nsresult PredictorLearnRedirect(nsIURI *targetURI,
+ nsIChannel *channel,
+ nsILoadContext *loadContext);
+
+} // mozilla::net
+} // mozilla
+%}
diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl
new file mode 100644
index 000000000..b00aecc07
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictorVerifier.idl
@@ -0,0 +1,40 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsINetworkPredictorVerifier - used for testing the network predictor to
+ * ensure it does what we expect it to do.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(2e43bb32-dabf-4494-9f90-2b3195b1c73d)]
+interface nsINetworkPredictorVerifier : nsISupports
+{
+ /**
+ * Callback for when we do a predictive prefetch
+ *
+ * @param uri - The URI that was prefetched
+ * @param status - The request status code returned by the
+ * prefetch attempt e.g. 200 (OK):w
+ */
+ void onPredictPrefetch(in nsIURI uri, in uint32_t status);
+
+ /**
+ * Callback for when we do a predictive preconnect
+ *
+ * @param uri - The URI that was preconnected to
+ */
+ void onPredictPreconnect(in nsIURI uri);
+
+ /**
+ * Callback for when we do a predictive DNS lookup
+ *
+ * @param uri - The URI that was looked up
+ */
+ void onPredictDNS(in nsIURI uri);
+};
diff --git a/netwerk/base/nsINetworkProperties.idl b/netwerk/base/nsINetworkProperties.idl
new file mode 100644
index 000000000..30f809324
--- /dev/null
+++ b/netwerk/base/nsINetworkProperties.idl
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* This interface provides supplemental information
+ to that which is provided by the network info definition. It is
+ reasonable to expect it to grow.
+*/
+
+
+[scriptable, builtinclass, uuid(0af94dec-7ffc-4301-8937-766c214ac688)]
+interface nsINetworkProperties : nsISupports
+{
+ readonly attribute boolean isWifi;
+ readonly attribute unsigned long dhcpGateway;
+};
diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl
new file mode 100644
index 000000000..6c03a2743
--- /dev/null
+++ b/netwerk/base/nsINullChannel.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is only used in order to mark the fact that
+ * an object isn't a complete implementation of its interfaces.
+ * For example, a consumer can QI NullHttpChannel to nsINullChannel,
+ * to determine if the object is just a dummy implementation of nsIHttpChannel.
+ */
+[scriptable, builtinclass, uuid(4610b901-df41-4bb4-bd3f-fd4d6b6d8d68)]
+interface nsINullChannel : nsISupports
+{
+};
diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
new file mode 100644
index 000000000..0da79c18a
--- /dev/null
+++ b/netwerk/base/nsIOService.cpp
@@ -0,0 +1,1880 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 cindent et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+
+#include "nsIOService.h"
+#include "nsIDOMNode.h"
+#include "nsIProtocolHandler.h"
+#include "nsIFileProtocolHandler.h"
+#include "nscore.h"
+#include "nsIURI.h"
+#include "prprf.h"
+#include "nsIErrorService.h"
+#include "netCore.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsXPCOM.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsSecCheckWrapChannel.h"
+#include "nsSimpleNestedURI.h"
+#include "nsTArray.h"
+#include "nsIConsoleService.h"
+#include "nsIUploadChannel2.h"
+#include "nsXULAppAPI.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsICancelable.h"
+#include "nsINetworkLinkService.h"
+#include "nsPISocketTransportService.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsURLHelper.h"
+#include "nsPIDNSService.h"
+#include "nsIProtocolProxyService2.h"
+#include "MainThreadUtils.h"
+#include "nsINode.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/CaptivePortalService.h"
+#include "ReferrerPolicy.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "xpcpublic.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#include "nsINetworkInterface.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+#define PORT_PREF_PREFIX "network.security.ports."
+#define PORT_PREF(x) PORT_PREF_PREFIX x
+#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
+#define OFFLINE_MIRRORS_CONNECTIVITY "network.offline-mirrors-connectivity"
+
+// Nb: these have been misnomers since bug 715770 removed the buffer cache.
+// "network.segment.count" and "network.segment.size" would be better names,
+// but the old names are still used to preserve backward compatibility.
+#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
+#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
+
+#define MAX_RECURSION_COUNT 50
+
+nsIOService* gIOService = nullptr;
+static bool gHasWarnedUploadChannel2;
+
+static LazyLogModule gIOServiceLog("nsIOService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args)
+
+// A general port blacklist. Connections to these ports will not be allowed
+// unless the protocol overrides.
+//
+// TODO: I am sure that there are more ports to be added.
+// This cut is based on the classic mozilla codebase
+
+int16_t gBadPortList[] = {
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp-data
+ 21, // ftp-cntl
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // NTP
+ 135, // loc-srv / epmap
+ 139, // netbios
+ 143, // imap2
+ 179, // BGP
+ 389, // ldap
+ 465, // smtp+ssl
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // Chat
+ 532, // netnews
+ 540, // uucp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, //
+ 601, //
+ 636, // ldap+ssl
+ 993, // imap+ssl
+ 995, // pop3+ssl
+ 2049, // nfs
+ 3659, // apple-sasl / PasswordServer
+ 4045, // lockd
+ 6000, // x11
+ 6665, // Alternate IRC [Apple addition]
+ 6666, // Alternate IRC [Apple addition]
+ 6667, // Standard IRC [Apple addition]
+ 6668, // Alternate IRC [Apple addition]
+ 6669, // Alternate IRC [Apple addition]
+ 0, // This MUST be zero so that we can populating the array
+};
+
+static const char kProfileChangeNetTeardownTopic[] = "profile-change-net-teardown";
+static const char kProfileChangeNetRestoreTopic[] = "profile-change-net-restore";
+static const char kProfileDoChange[] = "profile-do-change";
+
+// Necko buffer defaults
+uint32_t nsIOService::gDefaultSegmentSize = 4096;
+uint32_t nsIOService::gDefaultSegmentCount = 24;
+
+bool nsIOService::sTelemetryEnabled = false;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsIOService::nsIOService()
+ : mOffline(true)
+ , mOfflineForProfileChange(false)
+ , mManageLinkStatus(false)
+ , mConnectivity(true)
+ , mOfflineMirrorsConnectivity(true)
+ , mSettingOffline(false)
+ , mSetOfflineValue(false)
+ , mShutdown(false)
+ , mHttpHandlerAlreadyShutingDown(false)
+ , mNetworkLinkServiceInitialized(false)
+ , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY)
+ , mNetworkNotifyChanged(true)
+ , mLastOfflineStateChange(PR_IntervalNow())
+ , mLastConnectivityChange(PR_IntervalNow())
+ , mLastNetworkLinkChange(PR_IntervalNow())
+ , mNetTearingDownStarted(0)
+{
+}
+
+nsresult
+nsIOService::Init()
+{
+ nsresult rv;
+
+ // We need to get references to the DNS service so that we can shut it
+ // down later. If we wait until the nsIOService is being shut down,
+ // GetService will fail at that point.
+
+ mDNSService = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get DNS service");
+ return rv;
+ }
+
+ // XXX hack until xpidl supports error info directly (bug 13423)
+ nsCOMPtr<nsIErrorService> errorService = do_GetService(NS_ERRORSERVICE_CONTRACTID);
+ if (errorService) {
+ errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_NETWORK, NECKO_MSGS_URL);
+ }
+ else
+ NS_WARNING("failed to get error service");
+
+ InitializeCaptivePortalService();
+
+ // setup our bad port list stuff
+ for(int i=0; gBadPortList[i]; i++)
+ mRestrictedPortList.AppendElement(gBadPortList[i]);
+
+ // Further modifications to the port list come from prefs
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ if (prefBranch) {
+ prefBranch->AddObserver(PORT_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(MANAGE_OFFLINE_STATUS_PREF, this, true);
+ prefBranch->AddObserver(NECKO_BUFFER_CACHE_COUNT_PREF, this, true);
+ prefBranch->AddObserver(NECKO_BUFFER_CACHE_SIZE_PREF, this, true);
+ prefBranch->AddObserver(NETWORK_NOTIFY_CHANGED_PREF, this, true);
+ prefBranch->AddObserver(NETWORK_CAPTIVE_PORTAL_PREF, this, true);
+ PrefsChanged(prefBranch);
+ }
+
+ // Register for profile change notifications
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kProfileChangeNetTeardownTopic, true);
+ observerService->AddObserver(this, kProfileChangeNetRestoreTopic, true);
+ observerService->AddObserver(this, kProfileDoChange, true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ }
+ else
+ NS_WARNING("failed to get observer service");
+
+ Preferences::AddBoolVarCache(&sTelemetryEnabled, "toolkit.telemetry.enabled", false);
+ Preferences::AddBoolVarCache(&mOfflineMirrorsConnectivity, OFFLINE_MIRRORS_CONNECTIVITY, true);
+
+ gIOService = this;
+
+ InitializeNetworkLinkService();
+
+ SetOffline(false);
+
+ return NS_OK;
+}
+
+
+nsIOService::~nsIOService()
+{
+ gIOService = nullptr;
+}
+
+nsresult
+nsIOService::InitializeCaptivePortalService()
+{
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // We only initalize a captive portal service in the main process
+ return NS_OK;
+ }
+
+ mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (mCaptivePortalService) {
+ return static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Initialize();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::InitializeSocketTransportService()
+{
+ nsresult rv = NS_OK;
+
+ if (!mSocketTransportService) {
+ mSocketTransportService = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get socket transport service");
+ }
+ }
+
+ if (mSocketTransportService) {
+ rv = mSocketTransportService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
+ mSocketTransportService->SetOffline(false);
+ }
+
+ return rv;
+}
+
+nsresult
+nsIOService::InitializeNetworkLinkService()
+{
+ nsresult rv = NS_OK;
+
+ if (mNetworkLinkServiceInitialized)
+ return rv;
+
+ if (!NS_IsMainThread()) {
+ NS_WARNING("Network link service should be created on main thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ // go into managed mode if we can, and chrome process
+ if (XRE_IsParentProcess())
+ {
+ mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ }
+
+ if (mNetworkLinkService) {
+ mNetworkLinkServiceInitialized = true;
+ }
+
+ // After initializing the networkLinkService, query the connectivity state
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+
+ return rv;
+}
+
+nsIOService*
+nsIOService::GetInstance() {
+ if (!gIOService) {
+ gIOService = new nsIOService();
+ if (!gIOService)
+ return nullptr;
+ NS_ADDREF(gIOService);
+
+ nsresult rv = gIOService->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gIOService);
+ return nullptr;
+ }
+ return gIOService;
+ }
+ NS_ADDREF(gIOService);
+ return gIOService;
+}
+
+NS_IMPL_ISUPPORTS(nsIOService,
+ nsIIOService,
+ nsIIOService2,
+ nsINetUtil,
+ nsISpeculativeConnect,
+ nsIObserver,
+ nsIIOServiceInternal,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsIOService::RecheckCaptivePortal()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ if (mCaptivePortalService) {
+ mCaptivePortalService->RecheckCaptivePortal();
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan)
+{
+ nsresult rv;
+
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = newChan->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(host.BeginReading(), &prAddr) != PR_SUCCESS) {
+ // The redirect wasn't to an IP literal, so there's probably no need
+ // to trigger the captive portal detection right now. It can wait.
+ return NS_OK;
+ }
+
+ NetAddr netAddr;
+ PRNetAddrToNetAddr(&prAddr, &netAddr);
+ if (IsIPAddrLocal(&netAddr)) {
+ // Redirects to local IP addresses are probably captive portals
+ mCaptivePortalService->RecheckCaptivePortal();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper *helper)
+{
+ // If a redirect to a local network address occurs, then chances are we
+ // are in a captive portal, so we trigger a recheck.
+ RecheckCaptivePortalIfLocalRedirect(newChan);
+
+ // This is silly. I wish there was a simpler way to get at the global
+ // reference of the contentSecurityManager. But it lives in the XPCOM
+ // service registry.
+ nsCOMPtr<nsIChannelEventSink> sink =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ if (sink) {
+ nsresult rv = helper->DelegateOnChannelRedirect(sink, oldChan,
+ newChan, flags);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Finally, our category
+ nsCOMArray<nsIChannelEventSink> entries;
+ mChannelEventSinks.GetEntries(entries);
+ int32_t len = entries.Count();
+ for (int32_t i = 0; i < len; ++i) {
+ nsresult rv = helper->DelegateOnChannelRedirect(entries[i], oldChan,
+ newChan, flags);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIOService::CacheProtocolHandler(const char *scheme, nsIProtocolHandler *handler)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (unsigned int i=0; i<NS_N(gScheme); i++)
+ {
+ if (!nsCRT::strcasecmp(scheme, gScheme[i]))
+ {
+ nsresult rv;
+ NS_ASSERTION(!mWeakHandler[i], "Protocol handler already cached");
+ // Make sure the handler supports weak references.
+ nsCOMPtr<nsISupportsWeakReference> factoryPtr = do_QueryInterface(handler, &rv);
+ if (!factoryPtr)
+ {
+ // Don't cache handlers that don't support weak reference as
+ // there is real danger of a circular reference.
+#ifdef DEBUG_dp
+ printf("DEBUG: %s protcol handler doesn't support weak ref. Not cached.\n", scheme);
+#endif /* DEBUG_dp */
+ return NS_ERROR_FAILURE;
+ }
+ mWeakHandler[i] = do_GetWeakReference(handler);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsIOService::GetCachedProtocolHandler(const char *scheme, nsIProtocolHandler **result, uint32_t start, uint32_t end)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint32_t len = end - start - 1;
+ for (unsigned int i=0; i<NS_N(gScheme); i++)
+ {
+ if (!mWeakHandler[i])
+ continue;
+
+ // handle unterminated strings
+ // start is inclusive, end is exclusive, len = end - start - 1
+ if (end ? (!nsCRT::strncasecmp(scheme + start, gScheme[i], len)
+ && gScheme[i][len] == '\0')
+ : (!nsCRT::strcasecmp(scheme, gScheme[i])))
+ {
+ return CallQueryReferent(mWeakHandler[i].get(), result);
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+static bool
+UsesExternalProtocolHandler(const char* aScheme)
+{
+ if (NS_LITERAL_CSTRING("file").Equals(aScheme) ||
+ NS_LITERAL_CSTRING("chrome").Equals(aScheme) ||
+ NS_LITERAL_CSTRING("resource").Equals(aScheme)) {
+ // Don't allow file:, chrome: or resource: URIs to be handled with
+ // nsExternalProtocolHandler, since internally we rely on being able to
+ // use and read from these URIs.
+ return false;
+ }
+
+ nsAutoCString pref("network.protocol-handler.external.");
+ pref += aScheme;
+
+ return Preferences::GetBool(pref.get(), false);
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolHandler(const char* scheme, nsIProtocolHandler* *result)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(scheme);
+ // XXX we may want to speed this up by introducing our own protocol
+ // scheme -> protocol handler mapping, avoiding the string manipulation
+ // and service manager stuff
+
+ rv = GetCachedProtocolHandler(scheme, result);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ if (!UsesExternalProtocolHandler(scheme)) {
+ nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
+ contractID += scheme;
+ ToLowerCase(contractID);
+
+ rv = CallGetService(contractID.get(), result);
+ if (NS_SUCCEEDED(rv)) {
+ CacheProtocolHandler(scheme, *result);
+ return rv;
+ }
+
+#ifdef MOZ_ENABLE_GIO
+ // check to see whether GVFS can handle this URI scheme. if it can
+ // create a nsIURI for the "scheme:", then we assume it has support for
+ // the requested protocol. otherwise, we failover to using the default
+ // protocol handler.
+
+ rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"moz-gio",
+ result);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString spec(scheme);
+ spec.Append(':');
+
+ nsIURI *uri;
+ rv = (*result)->NewURI(spec, nullptr, nullptr, &uri);
+ if (NS_SUCCEEDED(rv)) {
+ NS_RELEASE(uri);
+ return rv;
+ }
+
+ NS_RELEASE(*result);
+ }
+#endif
+ }
+
+ // Okay we don't have a protocol handler to handle this url type, so use
+ // the default protocol handler. This will cause urls to get dispatched
+ // out to the OS ('cause we can't do anything with them) when we try to
+ // read from a channel created by the default protocol handler.
+
+ rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default",
+ result);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractScheme(const nsACString &inURI, nsACString &scheme)
+{
+ return net_ExtractURLScheme(inURI, scheme);
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolFlags(const char* scheme, uint32_t *flags)
+{
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // We can't call DoGetProtocolFlags here because we don't have a URI. This
+ // API is used by (and only used by) extensions, which is why it's still
+ // around. Calling this on a scheme with dynamic flags will throw.
+ rv = handler->GetProtocolFlags(flags);
+ return rv;
+}
+
+class AutoIncrement
+{
+ public:
+ explicit AutoIncrement(uint32_t *var) : mVar(var)
+ {
+ ++*var;
+ }
+ ~AutoIncrement()
+ {
+ --*mVar;
+ }
+ private:
+ uint32_t *mVar;
+};
+
+nsresult
+nsIOService::NewURI(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIURI **result)
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ static uint32_t recursionCount = 0;
+ if (recursionCount >= MAX_RECURSION_COUNT)
+ return NS_ERROR_MALFORMED_URI;
+ AutoIncrement inc(&recursionCount);
+
+ nsAutoCString scheme;
+ nsresult rv = ExtractScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // then aSpec is relative
+ if (!aBaseURI)
+ return NS_ERROR_MALFORMED_URI;
+
+ if (!aSpec.IsEmpty() && aSpec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ return aBaseURI->CloneWithNewRef(aSpec, result);
+ }
+
+ rv = aBaseURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // now get the handler for this scheme
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ return handler->NewURI(aSpec, aCharset, aBaseURI, result);
+}
+
+
+NS_IMETHODIMP
+nsIOService::NewFileURI(nsIFile *file, nsIURI **result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+
+ rv = GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler( do_QueryInterface(handler, &rv) );
+ if (NS_FAILED(rv)) return rv;
+
+ return fileHandler->NewFileURI(file, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI2(nsIURI* aURI,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ return NewChannelFromURIWithProxyFlags2(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannelFromURI2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI(nsIURI *aURI, nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannelFromURI2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannelFromURI()",
+ u"nsIOService::NewChannelFromURI2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ return NewChannelFromURI2(aURI,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(),
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadInfo,
+ result);
+}
+
+nsresult
+nsIOService::NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t protoFlags;
+ rv = handler->DoGetProtocolFlags(aURI, &protoFlags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ideally we are creating new channels by calling NewChannel2 (NewProxiedChannel2).
+ // Keep in mind that Addons can implement their own Protocolhandlers, hence
+ // NewChannel2() might *not* be implemented.
+ // We do not want to break those addons, therefore we first try to create a channel
+ // calling NewChannel2(); if that fails:
+ // * we fall back to creating a channel by calling NewChannel()
+ // * wrap the addon channel
+ // * and attach the loadInfo to the channel wrapper
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
+ if (pph) {
+ rv = pph->NewProxiedChannel2(aURI, nullptr, aProxyFlags, aProxyURI,
+ aLoadInfo, getter_AddRefs(channel));
+ // if calling NewProxiedChannel2() fails we try to fall back to
+ // creating a new proxied channel by calling NewProxiedChannel().
+ if (NS_FAILED(rv)) {
+ rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The protocol handler does not implement NewProxiedChannel2, so
+ // maybe we need to wrap the channel (see comment in MaybeWrap
+ // function).
+ channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo);
+ }
+ }
+ else {
+ rv = handler->NewChannel2(aURI, aLoadInfo, getter_AddRefs(channel));
+ // if calling newChannel2() fails we try to fall back to
+ // creating a new channel by calling NewChannel().
+ if (NS_FAILED(rv)) {
+ rv = handler->NewChannel(aURI, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The protocol handler does not implement NewChannel2, so
+ // maybe we need to wrap the channel (see comment in MaybeWrap
+ // function).
+ channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo);
+ }
+ }
+
+ // Make sure that all the individual protocolhandlers attach a loadInfo.
+ if (aLoadInfo) {
+ // make sure we have the same instance of loadInfo on the newly created channel
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (aLoadInfo != loadInfo) {
+ MOZ_ASSERT(false, "newly created channel must have a loadinfo attached");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (loadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+ }
+
+ // Some extensions override the http protocol handler and provide their own
+ // implementation. The channels returned from that implementation doesn't
+ // seem to always implement the nsIUploadChannel2 interface, presumably
+ // because it's a new interface.
+ // Eventually we should remove this and simply require that http channels
+ // implement the new interface.
+ // See bug 529041
+ if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel);
+ if (!uploadChannel2) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(NS_LITERAL_STRING(
+ "Http channel implementation doesn't support nsIUploadChannel2. An extension has supplied a non-functional http protocol handler. This will break behavior and in future releases not work at all."
+ ).get());
+ }
+ gHasWarnedUploadChannel2 = true;
+ }
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags2(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ // Ideally all callers of NewChannelFromURIWithProxyFlags2 provide the
+ // necessary arguments to create a loadinfo. Keep in mind that addons
+ // might still call NewChannelFromURIWithProxyFlags() which forwards
+ // its calls to NewChannelFromURIWithProxyFlags2 using *null* values
+ // as the arguments for aLoadingNode, aLoadingPrincipal, and also
+ // aTriggeringPrincipal.
+ // We do not want to break those addons, hence we only create a Loadinfo
+ // if 'aLoadingNode' or 'aLoadingPrincipal' are provided. Note, that
+ // either aLoadingNode or aLoadingPrincipal is required to succesfully
+ // create a LoadInfo object.
+ // Except in the case of top level TYPE_DOCUMENT loads, where the
+ // loadingNode and loadingPrincipal are allowed to have null values.
+ nsCOMPtr<nsILoadInfo> loadInfo;
+
+ // TYPE_DOCUMENT loads don't require a loadingNode or principal, but other
+ // types do.
+ if (aLoadingNode || aLoadingPrincipal ||
+ aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsINode> loadingNode(do_QueryInterface(aLoadingNode));
+ loadInfo = new LoadInfo(aLoadingPrincipal,
+ aTriggeringPrincipal,
+ loadingNode,
+ aSecurityFlags,
+ aContentPolicyType);
+ }
+ NS_ASSERTION(loadInfo, "Please pass security info when creating a channel");
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ aProxyURI,
+ aProxyFlags,
+ loadInfo,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannelFromURIWithProxyFlags2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags(nsIURI *aURI,
+ nsIURI *aProxyURI,
+ uint32_t aProxyFlags,
+ nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannelFromURIWithProxyFlags2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannelFromURIWithProxyFlags()",
+ u"nsIOService::NewChannelFromURIWithProxyFlags2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ return NewChannelFromURIWithProxyFlags2(aURI,
+ aProxyURI,
+ aProxyFlags,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(),
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannel2(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIDOMNode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ uint32_t aContentPolicyType,
+ nsIChannel** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ return NewChannelFromURI2(uri,
+ aLoadingNode,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ result);
+}
+
+/* ***** DEPRECATED *****
+ * please use NewChannel2 providing the right arguments for:
+ * * aLoadingNode
+ * * aLoadingPrincipal
+ * * aTriggeringPrincipal
+ * * aSecurityFlags
+ * * aContentPolicyType
+ *
+ * See nsIIoService.idl for a detailed description of those arguments
+ */
+NS_IMETHODIMP
+nsIOService::NewChannel(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIChannel **result)
+{
+ NS_ASSERTION(false, "Deprecated, use NewChannel2 providing loadInfo arguments!");
+
+ const char16_t* params[] = {
+ u"nsIOService::NewChannel()",
+ u"nsIOService::NewChannel2()"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ // Call NewChannel2 providing default arguments for the loadInfo.
+ return NewChannel2(aSpec,
+ aCharset,
+ aBaseURI,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(), // aLoadingPrincipal
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ result);
+}
+
+bool
+nsIOService::IsLinkUp()
+{
+ InitializeNetworkLinkService();
+
+ if (!mNetworkLinkService) {
+ // We cannot decide, assume the link is up
+ return true;
+ }
+
+ bool isLinkUp;
+ nsresult rv;
+ rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isLinkUp;
+}
+
+NS_IMETHODIMP
+nsIOService::GetOffline(bool *offline)
+{
+ if (mOfflineMirrorsConnectivity) {
+ *offline = mOffline || !mConnectivity;
+ } else {
+ *offline = mOffline;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetOffline(bool offline)
+{
+ LOG(("nsIOService::SetOffline offline=%d\n", offline));
+ // When someone wants to go online (!offline) after we got XPCOM shutdown
+ // throw ERROR_NOT_AVAILABLE to prevent return to online state.
+ if ((mShutdown || mOfflineForProfileChange) && !offline)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // SetOffline() may re-enter while it's shutting down services.
+ // If that happens, save the most recent value and it will be
+ // processed when the first SetOffline() call is done bringing
+ // down the service.
+ mSetOfflineValue = offline;
+ if (mSettingOffline) {
+ return NS_OK;
+ }
+
+ mSettingOffline = true;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (XRE_IsParentProcess()) {
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, offline ?
+ u"true" :
+ u"false");
+ }
+ }
+
+ nsIIOService *subject = static_cast<nsIIOService *>(this);
+ while (mSetOfflineValue != mOffline) {
+ offline = mSetOfflineValue;
+
+ if (offline && !mOffline) {
+ NS_NAMED_LITERAL_STRING(offlineString, NS_IOSERVICE_OFFLINE);
+ mOffline = true; // indicate we're trying to shutdown
+
+ // don't care if notifications fail
+ if (observerService)
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ offlineString.get());
+
+ if (mSocketTransportService)
+ mSocketTransportService->SetOffline(true);
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ if (observerService)
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ offlineString.get());
+ }
+ else if (!offline && mOffline) {
+ // go online
+ if (mDNSService) {
+ DebugOnly<nsresult> rv = mDNSService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service init failed");
+ }
+ InitializeSocketTransportService();
+ mOffline = false; // indicate success only AFTER we've
+ // brought up the services
+
+ // trigger a PAC reload when we come back online
+ if (mProxyService)
+ mProxyService->ReloadPAC();
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ // don't care if notification fails
+ // Only send the ONLINE notification if there is connectivity
+ if (observerService && mConnectivity) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ }
+ }
+ }
+
+ // Don't notify here, as the above notifications (if used) suffice.
+ if ((mShutdown || mOfflineForProfileChange) && mOffline) {
+ // be sure to try and shutdown both (even if the first fails)...
+ // shutdown dns service first, because it has callbacks for socket transport
+ if (mDNSService) {
+ DebugOnly<nsresult> rv = mDNSService->Shutdown();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed");
+ }
+ if (mSocketTransportService) {
+ DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
+ }
+ }
+
+ mSettingOffline = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetConnectivity(bool *aConnectivity)
+{
+ *aConnectivity = mConnectivity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetConnectivity(bool aConnectivity)
+{
+ LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity));
+ // This should only be called from ContentChild to pass the connectivity
+ // value from the chrome process to the content process.
+ if (XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetConnectivityInternal(aConnectivity);
+}
+
+nsresult
+nsIOService::SetConnectivityInternal(bool aConnectivity)
+{
+ LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n", aConnectivity));
+ if (mConnectivity == aConnectivity) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ mConnectivity = aConnectivity;
+
+ // This is used for PR_Connect PR_Close telemetry so it is important that
+ // we have statistic about network change event even if we are offline.
+ mLastConnectivityChange = PR_IntervalNow();
+
+ if (mCaptivePortalService) {
+ if (aConnectivity && !xpc::AreNonLocalConnectionsDisabled()) {
+ // This will also trigger a captive portal check for the new network
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+ // This notification sends the connectivity to the child processes
+ if (XRE_IsParentProcess()) {
+ observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC, aConnectivity ?
+ u"true" :
+ u"false");
+ }
+
+ if (mOffline) {
+ // We don't need to send any notifications if we're offline
+ return NS_OK;
+ }
+
+ if (aConnectivity) {
+ // If we were previously offline due to connectivity=false,
+ // send the ONLINE notification
+ observerService->NotifyObservers(
+ static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ } else {
+ // If we were previously online and lost connectivity
+ // send the OFFLINE notification
+ const nsLiteralString offlineString(u"" NS_IOSERVICE_OFFLINE);
+ observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ offlineString.get());
+ observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ offlineString.get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AllowPort(int32_t inPort, const char *scheme, bool *_retval)
+{
+ int16_t port = inPort;
+ if (port == -1) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (port == 0) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ // first check to see if the port is in our blacklist:
+ int32_t badPortListCnt = mRestrictedPortList.Length();
+ for (int i=0; i<badPortListCnt; i++)
+ {
+ if (port == mRestrictedPortList[i])
+ {
+ *_retval = false;
+
+ // check to see if the protocol wants to override
+ if (!scheme)
+ return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // let the protocol handler decide
+ return handler->AllowPort(port, scheme, _retval);
+ }
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsIOService::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ if (!prefs) return;
+
+ // Look for extra ports to block
+ if (!pref || strcmp(pref, PORT_PREF("banned")) == 0)
+ ParsePortList(prefs, PORT_PREF("banned"), false);
+
+ // ...as well as previous blocks to remove.
+ if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0)
+ ParsePortList(prefs, PORT_PREF("banned.override"), true);
+
+ if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) {
+ bool manage;
+ if (mNetworkLinkServiceInitialized &&
+ NS_SUCCEEDED(prefs->GetBoolPref(MANAGE_OFFLINE_STATUS_PREF,
+ &manage))) {
+ LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n", manage));
+ SetManageOfflineStatus(manage);
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) {
+ int32_t count;
+ if (NS_SUCCEEDED(prefs->GetIntPref(NECKO_BUFFER_CACHE_COUNT_PREF,
+ &count)))
+ /* check for bogus values and default if we find such a value */
+ if (count > 0)
+ gDefaultSegmentCount = count;
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) {
+ int32_t size;
+ if (NS_SUCCEEDED(prefs->GetIntPref(NECKO_BUFFER_CACHE_SIZE_PREF,
+ &size)))
+ /* check for bogus values and default if we find such a value
+ * the upper limit here is arbitrary. having a 1mb segment size
+ * is pretty crazy. if you remove this, consider adding some
+ * integer rollover test.
+ */
+ if (size > 0 && size < 1024*1024)
+ gDefaultSegmentSize = size;
+ NS_WARNING_ASSERTION(!(size & (size - 1)),
+ "network segment size is not a power of 2!");
+ }
+
+ if (!pref || strcmp(pref, NETWORK_NOTIFY_CHANGED_PREF) == 0) {
+ bool allow;
+ nsresult rv = prefs->GetBoolPref(NETWORK_NOTIFY_CHANGED_PREF, &allow);
+ if (NS_SUCCEEDED(rv)) {
+ mNetworkNotifyChanged = allow;
+ }
+ }
+
+ if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
+ bool captivePortalEnabled;
+ nsresult rv = prefs->GetBoolPref(NETWORK_CAPTIVE_PORTAL_PREF, &captivePortalEnabled);
+ if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
+ if (captivePortalEnabled && !xpc::AreNonLocalConnectionsDisabled()) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+ }
+}
+
+void
+nsIOService::ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove)
+{
+ nsXPIDLCString portList;
+
+ // Get a pref string and chop it up into a list of ports.
+ prefBranch->GetCharPref(pref, getter_Copies(portList));
+ if (portList) {
+ nsTArray<nsCString> portListArray;
+ ParseString(portList, ',', portListArray);
+ uint32_t index;
+ for (index=0; index < portListArray.Length(); index++) {
+ portListArray[index].StripWhitespace();
+ int32_t portBegin, portEnd;
+
+ if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin, &portEnd) == 2) {
+ if ((portBegin < 65536) && (portEnd < 65536)) {
+ int32_t curPort;
+ if (remove) {
+ for (curPort=portBegin; curPort <= portEnd; curPort++)
+ mRestrictedPortList.RemoveElement(curPort);
+ } else {
+ for (curPort=portBegin; curPort <= portEnd; curPort++)
+ mRestrictedPortList.AppendElement(curPort);
+ }
+ }
+ } else {
+ nsresult aErrorCode;
+ int32_t port = portListArray[index].ToInteger(&aErrorCode);
+ if (NS_SUCCEEDED(aErrorCode) && port < 65536) {
+ if (remove)
+ mRestrictedPortList.RemoveElement(port);
+ else
+ mRestrictedPortList.AppendElement(port);
+ }
+ }
+
+ }
+ }
+}
+
+void
+nsIOService::GetPrefBranch(nsIPrefBranch **result)
+{
+ *result = nullptr;
+ CallGetService(NS_PREFSERVICE_CONTRACTID, result);
+}
+
+class nsWakeupNotifier : public Runnable
+{
+public:
+ explicit nsWakeupNotifier(nsIIOServiceInternal *ioService)
+ :mIOService(ioService)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ return mIOService->NotifyWakeup();
+ }
+
+private:
+ virtual ~nsWakeupNotifier() { }
+ nsCOMPtr<nsIIOServiceInternal> mIOService;
+};
+
+NS_IMETHODIMP
+nsIOService::NotifyWakeup()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (observerService && mNetworkNotifyChanged) {
+ (void)observerService->
+ NotifyObservers(nullptr,
+ NS_NETWORK_LINK_TOPIC,
+ (u"" NS_NETWORK_LINK_DATA_CHANGED));
+ }
+
+ RecheckCaptivePortal();
+
+ return NS_OK;
+}
+
+void
+nsIOService::SetHttpHandlerAlreadyShutingDown()
+{
+ if (!mShutdown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ mHttpHandlerAlreadyShutingDown = true;
+ }
+}
+
+// nsIObserver interface
+NS_IMETHODIMP
+nsIOService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
+ if (prefBranch)
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
+ if (!mHttpHandlerAlreadyShutingDown) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+ if (!mOffline) {
+ mOfflineForProfileChange = true;
+ SetOffline(true);
+ }
+ } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
+ if (mOfflineForProfileChange) {
+ mOfflineForProfileChange = false;
+ SetOffline(false);
+ }
+ } else if (!strcmp(topic, kProfileDoChange)) {
+ if (data && NS_LITERAL_STRING("startup").Equals(data)) {
+ // Lazy initialization of network link service (see bug 620472)
+ InitializeNetworkLinkService();
+ // Set up the initilization flag regardless the actuall result.
+ // If we fail here, we will fail always on.
+ mNetworkLinkServiceInitialized = true;
+
+ // And now reflect the preference setting
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ PrefsChanged(prefBranch, MANAGE_OFFLINE_STATUS_PREF);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // Remember we passed XPCOM shutdown notification to prevent any
+ // changes of the offline status from now. We must not allow going
+ // online after this point.
+ mShutdown = true;
+
+ if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+
+ SetOffline(true);
+
+ if (mCaptivePortalService) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ mCaptivePortalService = nullptr;
+ }
+
+ // Break circular reference.
+ mProxyService = nullptr;
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ // coming back alive from sleep
+ // this indirection brought to you by:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
+ nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
+ NS_DispatchToMainThread(wakeupNotifier);
+ }
+
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ bool *aHadCharset,
+ nsACString &aContentType)
+{
+ net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseResponseContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ bool *aHadCharset,
+ nsACString &aContentType)
+{
+ net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ProtocolHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ NS_ENSURE_ARG(uri);
+
+ *result = false;
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Grab the protocol flags from the URI.
+ uint32_t protocolFlags;
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = handler->DoGetProtocolFlags(uri, &protocolFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = (protocolFlags & flags) == flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ nsresult rv = ProtocolHasFlags(uri, flags, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return rv;
+ }
+
+ // Dig deeper into the chain. Note that this is not a do/while loop to
+ // avoid the extra addref/release on |uri| in the common (non-nested) case.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> innerURI;
+ rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProtocolHasFlags(innerURI, flags, result);
+
+ if (*result) {
+ return rv;
+ }
+
+ nestedURI = do_QueryInterface(innerURI);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::ToImmutableURI(nsIURI* uri, nsIURI** result)
+{
+ if (!uri) {
+ *result = nullptr;
+ return NS_OK;
+ }
+
+ nsresult rv = NS_EnsureSafeToReturn(uri, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_TryToSetImmutable(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewSimpleNestedURI(nsIURI* aURI, nsIURI** aResult)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsCOMPtr<nsIURI> safeURI;
+ nsresult rv = NS_EnsureSafeToReturn(aURI, getter_AddRefs(safeURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = new nsSimpleNestedURI(safeURI));
+ return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsIOService::SetManageOfflineStatus(bool aManage)
+{
+ LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage));
+ mManageLinkStatus = aManage;
+
+ // When detection is not activated, the default connectivity state is true.
+ if (!mManageLinkStatus) {
+ SetConnectivityInternal(true);
+ return NS_OK;
+ }
+
+ InitializeNetworkLinkService();
+ // If the NetworkLinkService is already initialized, it does not call
+ // OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from
+ // false to true.
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetManageOfflineStatus(bool* aManage)
+{
+ *aManage = mManageLinkStatus;
+ return NS_OK;
+}
+
+// input argument 'data' is already UTF8'ed
+nsresult
+nsIOService::OnNetworkLinkEvent(const char *data)
+{
+ LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data));
+ if (!mNetworkLinkService)
+ return NS_ERROR_FAILURE;
+
+ if (mShutdown)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mManageLinkStatus) {
+ LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n"));
+ return NS_OK;
+ }
+
+ bool isUp = true;
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
+ mLastNetworkLinkChange = PR_IntervalNow();
+ // CHANGED means UP/DOWN didn't change
+ // but the status of the captive portal may have changed.
+ RecheckCaptivePortal();
+ return NS_OK;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
+ isUp = false;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
+ isUp = true;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
+ nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("Unhandled network event!");
+ return NS_OK;
+ }
+
+ return SetConnectivityInternal(isUp);
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeString(const nsACString& aString,
+ uint32_t aEscapeType,
+ nsACString& aResult)
+{
+ NS_ENSURE_ARG_MAX(aEscapeType, 4);
+
+ nsAutoCString stringCopy(aString);
+ nsCString result;
+
+ if (!NS_Escape(stringCopy, result, (nsEscapeMask) aEscapeType))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aResult.Assign(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeURL(const nsACString &aStr,
+ uint32_t aFlags, nsACString &aResult)
+{
+ aResult.Truncate();
+ NS_EscapeURL(aStr.BeginReading(), aStr.Length(),
+ aFlags | esc_AlwaysCopy, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::UnescapeString(const nsACString &aStr,
+ uint32_t aFlags, nsACString &aResult)
+{
+ aResult.Truncate();
+ NS_UnescapeURL(aStr.BeginReading(), aStr.Length(),
+ aFlags | esc_AlwaysCopy, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractCharsetFromContentType(const nsACString &aTypeHeader,
+ nsACString &aCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd,
+ bool *aHadCharset)
+{
+ nsAutoCString ignored;
+ net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
+ aCharsetStart, aCharsetEnd);
+ if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
+ *aHadCharset = false;
+ }
+ return NS_OK;
+}
+
+// parse policyString to policy enum value (see ReferrerPolicy.h)
+NS_IMETHODIMP
+nsIOService::ParseAttributePolicyString(const nsAString& policyString,
+ uint32_t *outPolicyEnum)
+{
+ NS_ENSURE_ARG(outPolicyEnum);
+ *outPolicyEnum = (uint32_t)AttributeReferrerPolicyFromString(policyString);
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+class IOServiceProxyCallback final : public nsIProtocolProxyCallback
+{
+ ~IOServiceProxyCallback() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ IOServiceProxyCallback(nsIInterfaceRequestor *aCallbacks,
+ nsIOService *aIOService)
+ : mCallbacks(aCallbacks)
+ , mIOService(aIOService)
+ { }
+
+private:
+ RefPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsIOService> mIOService;
+};
+
+NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
+
+NS_IMETHODIMP
+IOServiceProxyCallback::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
+ nsIProxyInfo *pi, nsresult status)
+{
+ // Checking proxy status for speculative connect
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi &&
+ NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ // proxies dont do speculative connect
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(),
+ getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+ do_QueryInterface(handler);
+ if (!speculativeHandler)
+ return NS_OK;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ nsCOMPtr<nsIPrincipal> principal;
+ if (loadInfo) {
+ principal = loadInfo->LoadingPrincipal();
+ }
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ speculativeHandler->SpeculativeAnonymousConnect2(uri, principal, mCallbacks);
+ } else {
+ speculativeHandler->SpeculativeConnect2(uri, principal, mCallbacks);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsIOService::SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool aAnonymous)
+{
+ if (IsNeckoChild()) {
+ ipc::URIParams params;
+ SerializeURI(aURI, params);
+ gNeckoChild->SendSpeculativeConnect(params,
+ IPC::Principal(aPrincipal),
+ aAnonymous);
+ return NS_OK;
+ }
+
+ // Check for proxy information. If there is a proxy configured then a
+ // speculative connect should not be performed because the potential
+ // reward is slim with tcp peers closely located to the browser.
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
+
+ // If the principal is given, we use this prinicpal directly. Otherwise,
+ // we fallback to use the system principal.
+ if (!aPrincipal) {
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(loadingPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // dummy channel used to create a TCP connection.
+ // we perform security checks on the *real* channel, responsible
+ // for any network loads. this real channel just checks the TCP
+ // pool if there is an available connection created by the
+ // channel we create underneath - hence it's safe to use
+ // the systemPrincipal as the loadingPrincipal for this channel.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NewChannelFromURI2(aURI,
+ nullptr, // aLoadingNode,
+ loadingPrincipal,
+ nullptr, //aTriggeringPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aAnonymous) {
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS;
+ channel->SetLoadFlags(loadFlags);
+ }
+
+ nsCOMPtr<nsICancelable> cancelable;
+ RefPtr<IOServiceProxyCallback> callback =
+ new IOServiceProxyCallback(aCallbacks, this);
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ return pps2->AsyncResolve2(channel, 0, callback, getter_AddRefs(cancelable));
+ }
+ return pps->AsyncResolve(channel, 0, callback, getter_AddRefs(cancelable));
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeAnonymousConnect(nsIURI *aURI,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeAnonymousConnect2(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks)
+{
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h
new file mode 100644
index 000000000..7ac23b791
--- /dev/null
+++ b/netwerk/base/nsIOService.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIOService_h__
+#define nsIOService_h__
+
+#include "nsStringFwd.h"
+#include "nsIIOService2.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsINetUtil.h"
+#include "nsIChannelEventSink.h"
+#include "nsCategoryCache.h"
+#include "nsISpeculativeConnect.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "prtime.h"
+#include "nsICaptivePortalService.h"
+
+#define NS_N(x) (sizeof(x)/sizeof(*x))
+
+// We don't want to expose this observer topic.
+// Intended internal use only for remoting offline/inline events.
+// See Bug 552829
+#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
+#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
+
+static const char gScheme[][sizeof("moz-safe-about")] =
+ {"chrome", "file", "http", "https", "jar", "data", "about", "moz-safe-about", "resource"};
+
+class nsINetworkLinkService;
+class nsIPrefBranch;
+class nsIProtocolProxyService2;
+class nsIProxyInfo;
+class nsPIDNSService;
+class nsPISocketTransportService;
+
+namespace mozilla {
+namespace net {
+class NeckoChild;
+class nsAsyncRedirectVerifyHelper;
+
+class nsIOService final : public nsIIOService2
+ , public nsIObserver
+ , public nsINetUtil
+ , public nsISpeculativeConnect
+ , public nsSupportsWeakReference
+ , public nsIIOServiceInternal
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIOSERVICE
+ NS_DECL_NSIIOSERVICE2
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINETUTIL
+ NS_DECL_NSISPECULATIVECONNECT
+ NS_DECL_NSIIOSERVICEINTERNAL
+
+ // Gets the singleton instance of the IO Service, creating it as needed
+ // Returns nullptr on out of memory or failure to initialize.
+ // Returns an addrefed pointer.
+ static nsIOService* GetInstance();
+
+ nsresult Init();
+ nsresult NewURI(const char* aSpec, nsIURI* aBaseURI,
+ nsIURI* *result,
+ nsIProtocolHandler* *hdlrResult);
+
+ // Called by channels before a redirect happens. This notifies the global
+ // redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper *helper);
+
+ bool IsOffline() { return mOffline; }
+ PRIntervalTime LastOfflineStateChange() { return mLastOfflineStateChange; }
+ PRIntervalTime LastConnectivityChange() { return mLastConnectivityChange; }
+ PRIntervalTime LastNetworkLinkChange() { return mLastNetworkLinkChange; }
+ bool IsNetTearingDown() { return mShutdown || mOfflineForProfileChange ||
+ mHttpHandlerAlreadyShutingDown; }
+ PRIntervalTime NetTearingDownStarted() { return mNetTearingDownStarted; }
+
+ // nsHttpHandler is going to call this function to inform nsIOService that network
+ // is in process of tearing down. Moving nsHttpConnectionMgr::Shutdown to nsIOService
+ // caused problems (bug 1242755) so we doing it in this way.
+ // As soon as nsIOService gets notification that it is shutdown it is going to
+ // reset mHttpHandlerAlreadyShutingDown.
+ void SetHttpHandlerAlreadyShutingDown();
+
+ bool IsLinkUp();
+
+ // Used to trigger a recheck of the captive portal status
+ nsresult RecheckCaptivePortal();
+private:
+ // These shouldn't be called directly:
+ // - construct using GetInstance
+ // - destroy using Release
+ nsIOService();
+ ~nsIOService();
+ nsresult SetConnectivityInternal(bool aConnectivity);
+
+ nsresult OnNetworkLinkEvent(const char *data);
+
+ nsresult GetCachedProtocolHandler(const char *scheme,
+ nsIProtocolHandler* *hdlrResult,
+ uint32_t start=0,
+ uint32_t end=0);
+ nsresult CacheProtocolHandler(const char *scheme,
+ nsIProtocolHandler* hdlr);
+
+ nsresult InitializeCaptivePortalService();
+ nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
+
+ // Prefs wrangling
+ void PrefsChanged(nsIPrefBranch *prefs, const char *pref = nullptr);
+ void GetPrefBranch(nsIPrefBranch **);
+ void ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove);
+
+ nsresult InitializeSocketTransportService();
+ nsresult InitializeNetworkLinkService();
+
+ // consolidated helper function
+ void LookupProxyInfo(nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags,
+ nsCString *aScheme, nsIProxyInfo **outPI);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result);
+
+ nsresult SpeculativeConnectInternal(nsIURI *aURI,
+ nsIPrincipal *aPrincipal,
+ nsIInterfaceRequestor *aCallbacks,
+ bool aAnonymous);
+
+private:
+ bool mOffline;
+ mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange;
+ bool mManageLinkStatus;
+ bool mConnectivity;
+ // If true, the connectivity state will be mirrored by IOService.offline
+ // meaning if !mConnectivity, GetOffline() will return true
+ bool mOfflineMirrorsConnectivity;
+
+ // Used to handle SetOffline() reentrancy. See the comment in
+ // SetOffline() for more details.
+ bool mSettingOffline;
+ bool mSetOfflineValue;
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mShutdown;
+ mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown;
+
+ nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
+ nsCOMPtr<nsPIDNSService> mDNSService;
+ nsCOMPtr<nsIProtocolProxyService2> mProxyService;
+ nsCOMPtr<nsICaptivePortalService> mCaptivePortalService;
+ nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
+ bool mNetworkLinkServiceInitialized;
+
+ // Cached protocol handlers, only accessed on the main thread
+ nsWeakPtr mWeakHandler[NS_N(gScheme)];
+
+ // cached categories
+ nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
+
+ nsTArray<int32_t> mRestrictedPortList;
+
+ bool mNetworkNotifyChanged;
+
+ static bool sTelemetryEnabled;
+
+ // These timestamps are needed for collecting telemetry on PR_Connect,
+ // PR_ConnectContinue and PR_Close blocking time. If we spend very long
+ // time in any of these functions we want to know if and what network
+ // change has happened shortly before.
+ mozilla::Atomic<PRIntervalTime> mLastOfflineStateChange;
+ mozilla::Atomic<PRIntervalTime> mLastConnectivityChange;
+ mozilla::Atomic<PRIntervalTime> mLastNetworkLinkChange;
+
+ // Time a network tearing down started.
+ mozilla::Atomic<PRIntervalTime> mNetTearingDownStarted;
+public:
+ // Used for all default buffer sizes that necko allocates.
+ static uint32_t gDefaultSegmentSize;
+ static uint32_t gDefaultSegmentCount;
+};
+
+/**
+ * Reference to the IO service singleton. May be null.
+ */
+extern nsIOService* gIOService;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsIOService_h__
diff --git a/netwerk/base/nsIParentChannel.idl b/netwerk/base/nsIParentChannel.idl
new file mode 100644
index 000000000..2858bb95e
--- /dev/null
+++ b/netwerk/base/nsIParentChannel.idl
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsITabParent;
+
+%{C++
+namespace mozilla {
+namespace net {
+class HttpChannelParentListener;
+}
+}
+%}
+
+[ptr] native HttpChannelParentListener(mozilla::net::HttpChannelParentListener);
+
+/**
+ * Implemented by chrome side of IPC protocols.
+ */
+
+[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)]
+interface nsIParentChannel : nsIStreamListener
+{
+ /**
+ * Called to set the HttpChannelParentListener object (optional).
+ */
+ [noscript] void setParentListener(in HttpChannelParentListener listener);
+
+ /**
+ * Called to notify the HttpChannelChild that tracking protection was
+ * disabled for this load.
+ */
+ [noscript] void notifyTrackingProtectionDisabled();
+
+ /**
+ * Called to invoke deletion of the IPC protocol.
+ */
+ void delete();
+};
diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl
new file mode 100644
index 000000000..df37a0131
--- /dev/null
+++ b/netwerk/base/nsIParentRedirectingChannel.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIParentChannel.idl"
+
+interface nsITabParent;
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implemented by chrome side of IPC protocols that support redirect responses.
+ */
+
+[scriptable, uuid(3ed1d288-5324-46ee-8a98-33ac37d1080b)]
+interface nsIParentRedirectingChannel : nsIParentChannel
+{
+ /**
+ * Called when the channel got a response that redirects it to a different
+ * URI. The implementation is responsible for calling the redirect observers
+ * on the child process and provide the decision result to the callback.
+ *
+ * @param newChannelId
+ * id of the redirect channel obtained from nsIRedirectChannelRegistrar.
+ * @param newURI
+ * the URI we redirect to
+ * @param callback
+ * redirect result callback, usage is compatible with how
+ * nsIChannelEventSink defines it
+ */
+ void startRedirect(in uint32_t newChannelId,
+ in nsIChannel newChannel,
+ in uint32_t redirectFlags,
+ in nsIAsyncVerifyRedirectCallback callback);
+
+ /**
+ * Called after we are done with redirecting process and we know if to
+ * redirect or not. Forward the redirect result to the child process. From
+ * that moment the nsIParentChannel implementation expects it will be
+ * forwarded all notifications from the 'real' channel.
+ *
+ * Primarilly used by HttpChannelParentListener::OnRedirectResult and kept
+ * as mActiveChannel and mRedirectChannel in that class.
+ */
+ void completeRedirect(in boolean succeeded);
+};
diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl
new file mode 100644
index 000000000..c5ddd90fe
--- /dev/null
+++ b/netwerk/base/nsIPermission.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)]
+/**
+ * This interface defines a "permission" object,
+ * used to specify allowed/blocked objects from
+ * user-specified sites (cookies, images etc).
+ */
+
+interface nsIPermission : nsISupports
+{
+ /**
+ * The principal for which this permission applies.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * a case-sensitive ASCII string, indicating the type of permission
+ * (e.g., "cookie", "image", etc).
+ * This string is specified by the consumer when adding a permission
+ * via nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute ACString type;
+
+ /**
+ * The permission (see nsIPermissionManager.idl for allowed values)
+ */
+ readonly attribute uint32_t capability;
+
+ /**
+ * The expiration type of the permission (session, time-based or none).
+ * Constants are EXPIRE_*, defined in nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute uint32_t expireType;
+
+ /**
+ * The expiration time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t expireTime;
+
+ /**
+ * Test whether a principal would be affected by this permission.
+ *
+ * @param principal the principal to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matches(in nsIPrincipal principal,
+ in boolean exactHost);
+
+ /**
+ * Test whether a URI would be affected by this permission.
+ * NOTE: This performs matches with default origin attribute values.
+ *
+ * @param uri the uri to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matchesURI(in nsIURI uri,
+ in boolean exactHost);
+};
diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl
new file mode 100644
index 000000000..b61817d4c
--- /dev/null
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains an interface to the Permission Manager,
+ * used to persistenly store permissions for different object types (cookies,
+ * images etc) on a site-by-site basis.
+ *
+ * This service broadcasts the following notification when the permission list
+ * is changed:
+ *
+ * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION)
+ * broadcast whenever the permission list changes in some way. there
+ * are four possible data strings for this notification; one
+ * notification will be broadcast for each change, and will involve
+ * a single permission.
+ * subject: an nsIPermission interface pointer representing the permission object
+ * that changed.
+ * data : "deleted"
+ * a permission was deleted. the subject is the deleted permission.
+ * "added"
+ * a permission was added. the subject is the added permission.
+ * "changed"
+ * a permission was changed. the subject is the new permission.
+ * "cleared"
+ * the entire permission list was cleared. the subject is null.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIObserver;
+interface nsIPrincipal;
+interface mozIDOMWindow;
+interface nsIPermission;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)]
+interface nsIPermissionManager : nsISupports
+{
+ /**
+ * Predefined return values for the testPermission method and for
+ * the permission param of the add method
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ */
+ const uint32_t UNKNOWN_ACTION = 0;
+ const uint32_t ALLOW_ACTION = 1;
+ const uint32_t DENY_ACTION = 2;
+ const uint32_t PROMPT_ACTION = 3;
+
+ /**
+ * Predefined expiration types for permissions. Permissions can be permanent
+ * (never expire), expire at the end of the session, or expire at a specified
+ * time. Permissions that expire at the end of a session may also have a
+ * specified expiration time.
+ */
+ const uint32_t EXPIRE_NEVER = 0;
+ const uint32_t EXPIRE_SESSION = 1;
+ const uint32_t EXPIRE_TIME = 2;
+
+ /**
+ * Add permission information for a given URI and permission type. This
+ * operation will cause the type string to be registered if it does not
+ * currently exist. If a permission already exists for a given type, it
+ * will be modified.
+ *
+ * @param uri the uri to add the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * Consumers should choose this string to be unique, with
+ * respect to other consumers.
+ * @param permission an integer representing the desired action (e.g. allow
+ * or deny). The interpretation of this number is up to the
+ * consumer, and may represent different actions for different
+ * types. Consumers may use one of the enumerated permission
+ * actions defined above, for convenience.
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ * @param expiretype a constant defining whether this permission should
+ * never expire (EXPIRE_NEVER), expire at the end of the
+ * session (EXPIRE_SESSION), or expire at a specified time
+ * (EXPIRE_TIME).
+ * @param expiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since Jan 1 1970 0:00:00).
+ */
+ void add(in nsIURI uri,
+ in string type,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Get all custom permissions for a given URI. This will return
+ * an enumerator of all permissions which are not set to default
+ * and which belong to the matching prinicpal of the given URI.
+ *
+ * @param uri the URI to get all permissions for
+ */
+ nsISimpleEnumerator getAllForURI(in nsIURI uri);
+
+ /**
+ * Add permission information for a given principal.
+ * It is internally calling the other add() method using the nsIURI from the
+ * principal.
+ * Passing a system principal will be a no-op because they will always be
+ * granted permissions.
+ */
+ void addFromPrincipal(in nsIPrincipal principal, in string typed,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Remove permission information for a given URI and permission type. This will
+ * remove the permission for the entire host described by the uri, acting as the
+ * opposite operation to the add() method.
+ *
+ * @param uri the uri to remove the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * The type must have been previously registered using the
+ * add() method.
+ */
+ void remove(in nsIURI uri,
+ in string type);
+
+ /**
+ * Remove permission information for a given principal.
+ * This is internally calling remove() with the host from the principal's URI.
+ * Passing system principal will be a no-op because we never add them to the
+ * database.
+ */
+ void removeFromPrincipal(in nsIPrincipal principal, in string type);
+
+ /**
+ * Remove the given permission from the permission manager.
+ *
+ * @param perm a permission obtained from the permission manager.
+ */
+ void removePermission(in nsIPermission perm);
+
+ /**
+ * Clear permission information for all websites.
+ */
+ void removeAll();
+
+ /**
+ * Clear all permission information added since the specified time.
+ */
+ void removeAllSince(in int64_t since);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether the principal associated with the window's document has the
+ * permission to perform a given action. System principals will always
+ * have permissions granted.
+ */
+ uint32_t testPermissionFromWindow(in mozIDOMWindow window,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * This requires an exact hostname match, subdomains are not a match.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * See testExactPermission() above.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action
+ * ignoring active sessions.
+ * System principals will always have permissions granted.
+ *
+ * @param principal the principal
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermanentPermission(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Get the permission object associated with the given principal and action.
+ * @param principal The principal
+ * @param type A case-sensitive ASCII string identifying the consumer
+ * @param exactHost If true, only the specific host will be matched,
+ * @see testExactPermission. If false, subdomains will
+ * also be searched, @see testPermission.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost);
+
+ /**
+ * Allows enumeration of all stored permissions
+ * @return an nsISimpleEnumerator interface that allows access to
+ * nsIPermission objects
+ */
+ readonly attribute nsISimpleEnumerator enumerator;
+
+ /**
+ * Remove all permissions that will match the origin pattern.
+ */
+ void removePermissionsWithAttributes(in DOMString patternAsJSON);
+
+ /**
+ * If the current permission is set to expire, reset the expiration time. If
+ * there is no permission or the current permission does not expire, this
+ * method will silently return.
+ *
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_SESSION.
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_TIME.
+ */
+ void updateExpireTime(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost,
+ in uint64_t sessionExpireTime,
+ in uint64_t persistentExpireTime);
+
+ /**
+ * Remove all current permission settings and get permission settings from
+ * chrome process.
+ */
+ void refreshPermission();
+};
+
+%{ C++
+#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
+
+#define PERM_CHANGE_NOTIFICATION "perm-changed"
+%}
diff --git a/netwerk/base/nsIPrivateBrowsingChannel.idl b/netwerk/base/nsIPrivateBrowsingChannel.idl
new file mode 100644
index 000000000..3bc822f01
--- /dev/null
+++ b/netwerk/base/nsIPrivateBrowsingChannel.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is implemented by channels which support overriding the
+ * privacy state of the channel.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(df702bb0-55b8-11e2-bcfd-0800200c9a66)]
+interface nsIPrivateBrowsingChannel : nsISupports
+{
+ /**
+ * Determine whether the channel is tied to a private browsing window.
+ *
+ * This value can be set only before the channel is opened. Setting it
+ * after that does not have any effect. This value overrides the privacy
+ * state of the channel, which means that if you call this method, then
+ * the loadGroup and load context will no longer be consulted when we
+ * need to know the private mode status for a channel.
+ *
+ * Note that this value is only meant to be used when the channel's privacy
+ * status cannot be obtained from the loadGroup or load context (for
+ * example, when the channel is not associated with any loadGroup or load
+ * context.) Setting this value directly should be avoided if possible.
+ *
+ * Implementations must enforce the ordering semantics of this function by
+ * raising errors if setPrivate is called on a channel which has a loadGroup
+ * and/or callbacks that implement nsILoadContext, or if the loadGroup
+ * or notificationCallbacks are set after setPrivate has been called.
+ *
+ * @param aPrivate whether the channel should be opened in private mode.
+ */
+ void setPrivate(in boolean aPrivate);
+
+ /**
+ * States whether the channel is in private browsing mode. This may either
+ * happen because the channel is opened from a private mode context or
+ * when the mode is explicitly set with ::setPrivate().
+ *
+ * This attribute is equivalent to NS_UsePrivateBrowsing(), but scriptable.
+ */
+ readonly attribute boolean isChannelPrivate;
+
+ /*
+ * This function is used to determine whether the channel's private mode
+ * has been overridden by a call to setPrivate. It is intended to be used
+ * by NS_UsePrivateBrowsing(), and you should not call it directly.
+ *
+ * @param aValue the overridden value. This will only be set if the function
+ * returns true.
+ */
+ [noscript] boolean isPrivateModeOverriden(out boolean aValue);
+};
diff --git a/netwerk/base/nsIProgressEventSink.idl b/netwerk/base/nsIProgressEventSink.idl
new file mode 100644
index 000000000..68f8bf059
--- /dev/null
+++ b/netwerk/base/nsIProgressEventSink.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIRequest;
+
+/**
+ * nsIProgressEventSink
+ *
+ * This interface is used to asynchronously convey channel status and progress
+ * information that is generally not critical to the processing of the channel.
+ * The information is intended to be displayed to the user in some meaningful
+ * way.
+ *
+ * An implementation of this interface can be passed to a channel via the
+ * channel's notificationCallbacks attribute. See nsIChannel for more info.
+ *
+ * The channel will begin passing notifications to the progress event sink
+ * after its asyncOpen method has been called. Notifications will cease once
+ * the channel calls its listener's onStopRequest method or once the channel
+ * is canceled (via nsIRequest::cancel).
+ *
+ * NOTE: This interface is actually not specific to channels and may be used
+ * with other implementations of nsIRequest.
+ */
+[scriptable, uuid(87d55fba-cb7e-4f38-84c1-5c6c2b2a55e9)]
+interface nsIProgressEventSink : nsISupports
+{
+ /**
+ * Called to notify the event sink that progress has occurred for the
+ * given request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aContext
+ * if aRequest is a channel, then this parameter is the listener
+ * context passed to nsIChannel::asyncOpen.
+ * @param aProgress
+ * numeric value in the range 0 to aProgressMax indicating the
+ * number of bytes transfered thus far.
+ * @param aProgressMax
+ * numeric value indicating maximum number of bytes that will be
+ * transfered (or -1 if total is unknown).
+ */
+ void onProgress(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in long long aProgress,
+ in long long aProgressMax);
+
+ /**
+ * Called to notify the event sink with a status message for the given
+ * request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aContext
+ * if aRequest is a channel, then this parameter is the listener
+ * context passed to nsIChannel::asyncOpen.
+ * @param aStatus
+ * status code (not necessarily an error code) indicating the
+ * state of the channel (usually the state of the underlying
+ * transport). see nsISocketTransport for socket specific status
+ * codes.
+ * @param aStatusArg
+ * status code argument to be used with the string bundle service
+ * to convert the status message into localized, human readable
+ * text. the meaning of this parameter is specific to the value
+ * of the status code. for socket status codes, this parameter
+ * indicates the host:port associated with the status code.
+ */
+ void onStatus(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus,
+ in wstring aStatusArg);
+
+};
diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl
new file mode 100644
index 000000000..e68c1a21c
--- /dev/null
+++ b/netwerk/base/nsIPrompt.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the prompt interface which can be used without knowlege of a
+ * parent window. The parentage is hidden by the GetInterface though
+ * which it is gotten. This interface is identical to nsIPromptService
+ * but without the parent nsIDOMWindow parameter. See nsIPromptService
+ * for all documentation.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting
+ * an & before the accesskey character. For a real &, use && instead.
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)]
+interface nsIPrompt : nsISupports
+{
+ void alert(in wstring dialogTitle,
+ in wstring text);
+
+ void alertCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean confirm(in wstring dialogTitle,
+ in wstring text);
+
+ boolean confirmCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24;
+
+ /* used for security dialogs, buttons are initially disabled */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+ int32_t confirmEx(in wstring dialogTitle,
+ in wstring text,
+ in unsigned long buttonFlags,
+ in wstring button0Title,
+ in wstring button1Title,
+ in wstring button2Title,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ inout wstring value,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring username,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean select(in wstring dialogTitle,
+ in wstring text,
+ in uint32_t count,
+ [array, size_is(count)] in wstring selectList,
+ out long outSelection);
+};
diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl
new file mode 100644
index 000000000..95dc00ece
--- /dev/null
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+/**
+ * nsIProtocolHandlerWithDynamicFlags
+ *
+ * Protocols that wish to return different flags depending on the URI should
+ * implement this interface.
+ */
+[scriptable, builtinclass, uuid(65a8e823-0591-4fc0-a56a-03265e0a4ce8)]
+interface nsIProtocolHandlerWithDynamicFlags : nsISupports
+{
+ /*
+ * Returns protocol flags for the given URI, which may be different from the
+ * flags for another URI of the same scheme.
+ */
+ unsigned long getFlagsForURI(in nsIURI aURI);
+};
+
+/**
+ * nsIProtocolHandler
+ */
+[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)]
+interface nsIProtocolHandler : nsISupports
+{
+ /**
+ * The scheme of this protocol (e.g., "file").
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * The default port is the port that this protocol normally uses.
+ * If a port does not make sense for the protocol (e.g., "about:")
+ * then -1 will be returned.
+ */
+ readonly attribute long defaultPort;
+
+ /**
+ * Returns the protocol specific flags (see flag definitions below).
+ */
+ readonly attribute unsigned long protocolFlags;
+
+%{C++
+ // Helper method to get the protocol flags in the right way.
+ nsresult DoGetProtocolFlags(nsIURI* aURI, uint32_t* aFlags)
+ {
+ nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dh = do_QueryInterface(this);
+ return dh ? dh->GetFlagsForURI(aURI, aFlags) : GetProtocolFlags(aFlags);
+ }
+%}
+
+ /**
+ * Makes a URI object that is suitable for loading by this protocol,
+ * where the URI string is given as an UTF-8 string. The caller may
+ * provide the charset from which the URI string originated, so that
+ * the URI string can be translated back to that charset (if necessary)
+ * before communicating with, for example, the origin server of the URI
+ * string. (Many servers do not support UTF-8 IRIs at the present time,
+ * so we must be careful about tracking the native charset of the origin
+ * server.)
+ *
+ * @param aSpec - the URI string in UTF-8 encoding. depending
+ * on the protocol implementation, unicode character
+ * sequences may or may not be %xx escaped.
+ * @param aOriginCharset - the charset of the document from which this URI
+ * string originated. this corresponds to the
+ * charset that should be used when communicating
+ * this URI to an origin server, for example. if
+ * null, then UTF-8 encoding is assumed (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec may be resolved relative
+ * to aBaseURI, depending on the protocol.
+ * If the protocol has no concept of relative
+ * URI aBaseURI will simply be ignored.
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler and
+ * sets the loadInfo for the constructed channel.
+ */
+ nsIChannel newChannel2(in nsIURI aURI, in nsILoadInfo aLoadinfo);
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler.
+ */
+ nsIChannel newChannel(in nsIURI aURI);
+
+ /**
+ * Allows a protocol to override blacklisted ports.
+ *
+ * This method will be called when there is an attempt to connect to a port
+ * that is blacklisted. For example, for most protocols, port 25 (Simple Mail
+ * Transfer) is banned. When a URI containing this "known-to-do-bad-things"
+ * port number is encountered, this function will be called to ask if the
+ * protocol handler wants to override the ban.
+ */
+ boolean allowPort(in long port, in string scheme);
+
+
+ /**************************************************************************
+ * Constants for the protocol flags (the first is the default mask, the
+ * others are deviations):
+ *
+ * NOTE: Implementation must ignore any flags they do not understand.
+ */
+
+ /**
+ * standard full URI with authority component and concept of relative
+ * URIs (http, ftp, ...)
+ */
+ const unsigned long URI_STD = 0;
+
+ /**
+ * no concept of relative URIs (about, javascript, finger, ...)
+ */
+ const unsigned long URI_NORELATIVE = (1<<0);
+
+ /**
+ * no authority component (file, ...)
+ */
+ const unsigned long URI_NOAUTH = (1<<1);
+
+ /**
+ * This protocol handler can be proxied via a proxy (socks or http)
+ * (e.g., irc, smtp, http, etc.). If the protocol supports transparent
+ * proxying, the handler should implement nsIProxiedProtocolHandler.
+ *
+ * If it supports only HTTP proxying, then it need not support
+ * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
+ * flag (see below).
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+ const unsigned long ALLOWS_PROXY = (1<<2);
+
+ /**
+ * This protocol handler can be proxied using a http proxy (e.g., http,
+ * ftp, etc.). nsIIOService::newChannelFromURI will feed URIs from this
+ * protocol handler to the HTTP protocol handler instead. This flag is
+ * ignored if ALLOWS_PROXY is not set.
+ */
+ const unsigned long ALLOWS_PROXY_HTTP = (1<<3);
+
+ /**
+ * The URIs for this protocol have no inherent security context, so
+ * documents loaded via this protocol should inherit the security context
+ * from the document that loads them.
+ */
+ const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4);
+
+ /**
+ * "Automatic" loads that would replace the document (e.g. <meta> refresh,
+ * certain types of XLinks, possibly other loads that the application
+ * decides are not user triggered) are not allowed if the originating (NOT
+ * the target) URI has this protocol flag. Note that the decision as to
+ * what constitutes an "automatic" load is made externally, by the caller
+ * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that
+ * method for more information.
+ *
+ * A typical protocol that might want to set this flag is a protocol that
+ * shows highly untrusted content in a viewing area that the user expects
+ * to have a lot of control over, such as an e-mail reader.
+ */
+ const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5);
+
+ /**
+ * +-------------------------------------------------------------------+
+ * | |
+ * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * These flags are used to determine who is allowed to load URIs for this
+ * protocol. Note that if a URI is nested, only the flags for the
+ * innermost URI matter. See nsINestedURI.
+ *
+ * If none of these five flags are set, the URI must be treated as if it
+ * had the URI_LOADABLE_BY_ANYONE flag set, for compatibility with protocol
+ * handlers written against Gecko 1.8 or earlier. In this case, there may
+ * be run-time warning messages indicating that a "default insecure"
+ * assumption is being made. At some point in the futures (Mozilla 2.0,
+ * most likely), these warnings will become errors.
+ */
+
+ /**
+ * The URIs for this protocol can be loaded by anyone. For example, any
+ * website should be allowed to trigger a load of a URI for this protocol.
+ * Web-safe protocols like "http" should set this flag.
+ */
+ const unsigned long URI_LOADABLE_BY_ANYONE = (1<<6);
+
+ /**
+ * The URIs for this protocol are UNSAFE if loaded by untrusted (web)
+ * content and may only be loaded by privileged code (for example, code
+ * which has the system principal). Various internal protocols should set
+ * this flag.
+ */
+ const unsigned long URI_DANGEROUS_TO_LOAD = (1<<7);
+
+ /**
+ * The URIs for this protocol point to resources that are part of the
+ * application's user interface. There are cases when such resources may
+ * be made accessible to untrusted content such as web pages, so this is
+ * less restrictive than URI_DANGEROUS_TO_LOAD but more restrictive than
+ * URI_LOADABLE_BY_ANYONE. See the documentation for
+ * nsIScriptSecurityManager::CheckLoadURI.
+ */
+ const unsigned long URI_IS_UI_RESOURCE = (1<<8);
+
+ /**
+ * Loading of URIs for this protocol from other origins should only be
+ * allowed if those origins should have access to the local filesystem.
+ * It's up to the application to decide what origins should have such
+ * access. Protocols like "file" that point to local data should set this
+ * flag.
+ */
+ const unsigned long URI_IS_LOCAL_FILE = (1<<9);
+
+ /**
+ * The URIs for this protocol can be loaded only by callers with a
+ * principal that subsumes this uri. For example, privileged code and
+ * websites that are same origin as this uri.
+ */
+ const unsigned long URI_LOADABLE_BY_SUBSUMERS = (1<<10);
+
+ /**
+ * Channels using this protocol never call OnDataAvailable
+ * on the listener passed to AsyncOpen and they therefore
+ * do not return any data that we can use.
+ */
+ const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11);
+
+ /**
+ * URIs for this protocol are considered to be local resources. This could
+ * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE),
+ * or something else that would not hit the network.
+ */
+ const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12);
+
+ /**
+ * URIs for this protocol execute script when they are opened.
+ */
+ const unsigned long URI_OPENING_EXECUTES_SCRIPT = (1<<13);
+
+ /**
+ * Loading channels from this protocol has side-effects that make
+ * it unsuitable for saving to a local file.
+ */
+ const unsigned long URI_NON_PERSISTABLE = (1<<14);
+
+ /**
+ * This protocol handler forbids accessing cookies e.g. for mail related
+ * protocols.
+ */
+ const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1<<15);
+
+ /**
+ * URIs for this protocol require the webapps permission on the principal
+ * when opening URIs for a different domain. See bug#773886
+ */
+ const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<16);
+
+ /**
+ * Channels for this protocol don't need to spin the event loop to handle
+ * Open() and reads on the resulting stream.
+ */
+ const unsigned long URI_SYNC_LOAD_IS_OK = (1<<17);
+
+ /**
+ * URI is secure to load in an https page and should not be blocked
+ * by nsMixedContentBlocker
+ */
+ const unsigned long URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT = (1<<18);
+
+ /**
+ * This URI may be fetched and the contents are visible to anyone. This is
+ * semantically equivalent to the resource being served with all-access CORS
+ * headers.
+ */
+ const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 19);
+
+ /**
+ * If this flag is set, then the origin for this protocol is the full URI
+ * spec, not just the scheme + host + port.
+ */
+ const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 20);
+
+ /**
+ * If this flag is set, the URI does not always allow content using the same
+ * protocol to link to it.
+ */
+ const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 21);
+};
+
+%{C++
+/**
+ * Protocol handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "@mozilla.org/network/protocol;1?name="
+/**
+ * For example, "@mozilla.org/network/protocol;1?name=http"
+ */
+
+#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1
+%}
diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl
new file mode 100644
index 000000000..96c2181ec
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyCallback.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProxyInfo;
+interface nsICancelable;
+
+/**
+ * This interface serves as a closure for nsIProtocolProxyService's
+ * asyncResolve method.
+ */
+[scriptable, uuid(fbb6eff6-0cc2-4d99-8d6f-0a12b462bdeb)]
+interface nsIProtocolProxyCallback : nsISupports
+{
+ /**
+ * This method is called when proxy info is available or when an error
+ * in the proxy resolution occurs.
+ *
+ * @param aRequest
+ * The value returned from asyncResolve.
+ * @param aChannel
+ * The channel passed to asyncResolve.
+ * @param aProxyInfo
+ * The resulting proxy info or null if there is no associated proxy
+ * info for aURI. As with the result of nsIProtocolProxyService's
+ * resolve method, a null result implies that a direct connection
+ * should be used.
+ * @param aStatus
+ * The status of the callback. This is a failure code if the request
+ * could not be satisfied, in which case the value of aStatus
+ * indicates the reason for the failure and aProxyInfo will be null.
+ */
+ void onProxyAvailable(in nsICancelable aRequest,
+ in nsIChannel aChannel,
+ in nsIProxyInfo aProxyInfo,
+ in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIProtocolProxyFilter.idl b/netwerk/base/nsIProtocolProxyFilter.idl
new file mode 100644
index 000000000..8798a49b4
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyFilter.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProtocolProxyService;
+interface nsIProxyInfo;
+interface nsIURI;
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * URI. Use nsIProtocolProxyService::registerFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyChannelFilter.
+ */
+[scriptable, uuid(f424abd3-32b4-456c-9f45-b7e3376cb0d1)]
+interface nsIProtocolProxyFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given URI
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aProxyService
+ * A reference to the Protocol Proxy Service. This is passed so that
+ * implementations may easily access methods such as newProxyInfo.
+ * @param aURI
+ * The URI for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given URI. This may be null.
+ *
+ * @return The proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use aProxyService.newProxyInfo to
+ * construct nsIProxyInfo objects.
+ */
+ nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService,
+ in nsIURI aURI, in nsIProxyInfo aProxy);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * channel. Use nsIProtocolProxyService::registerChannelFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyFilter.
+ */
+[scriptable, uuid(245b0880-82c5-4e6e-be6d-bc586aa55a90)]
+interface nsIProtocolProxyChannelFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given channel
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aProxyService
+ * A reference to the Protocol Proxy Service. This is passed so that
+ * implementations may easily access methods such as newProxyInfo.
+ * @param aChannel
+ * The channel for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given channel. This may be null.
+ *
+ * @return The proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use aProxyService.newProxyInfo to
+ * construct nsIProxyInfo objects.
+ */
+ nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService,
+ in nsIChannel aChannel, in nsIProxyInfo aProxy);
+};
diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl
new file mode 100644
index 000000000..7bbaa8d85
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -0,0 +1,281 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIProtocolProxyCallback;
+interface nsIProtocolProxyFilter;
+interface nsIProtocolProxyChannelFilter;
+interface nsIProxyInfo;
+interface nsIChannel;
+interface nsIURI;
+
+/**
+ * nsIProtocolProxyService provides methods to access information about
+ * various network proxies.
+ */
+[scriptable, uuid(ef57c8b6-e09d-4cd4-9222-2a5d2402e15d)]
+interface nsIProtocolProxyService : nsISupports
+{
+ /** Flag 1 << 0 is unused **/
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the SOCKS proxy
+ * to HTTP ones.
+ */
+ const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to not analyze the uri's
+ * scheme specific proxy. When this flag is set the main HTTP proxy is the
+ * preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is
+ * the preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy
+ * is the preferred one.
+ */
+ const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the HTTPS proxy
+ * to the others HTTP ones.
+ *
+ * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag.
+ *
+ * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME.
+ */
+ const unsigned long RESOLVE_PREFER_HTTPS_PROXY =
+ (1 << 3) | RESOLVE_IGNORE_URI_SCHEME;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to that all methods will be tunneled via
+ * CONNECT through the http proxy.
+ */
+ const unsigned long RESOLVE_ALWAYS_TUNNEL = (1 << 4);
+
+ /**
+ * This method returns via callback a nsIProxyInfo instance that identifies
+ * a proxy to be used for the given channel. Otherwise, this method returns
+ * null indicating that a direct connection should be used.
+ *
+ * @param aChannelOrURI
+ * The channel for which a proxy is to be found, or, if no channel is
+ * available, a URI indicating the same. This method will return
+ * NS_ERROR_NOINTERFACE if this argument isn't either an nsIURI or an
+ * nsIChannel.
+ * @param aFlags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior. Any additional bits that do
+ * not correspond to a RESOLVE_ flag are reserved for future use.
+ * @param aCallback
+ * The object to be notified when the result is available.
+ *
+ * @return An object that can be used to cancel the asychronous operation.
+ * If canceled, the cancelation status (aReason) will be forwarded
+ * to the callback's onProxyAvailable method via the aStatus param.
+ *
+ * NOTE: If this proxy is unavailable, getFailoverForProxy may be called
+ * to determine the correct secondary proxy to be used.
+ *
+ * NOTE: If the protocol handler for the given URI supports
+ * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from
+ * resolve may be passed to the newProxiedChannel method to create a
+ * nsIChannel to the given URI that uses the specified proxy.
+ *
+ * NOTE: However, if the nsIProxyInfo type is "http", then it means that
+ * the given URI should be loaded using the HTTP protocol handler, which
+ * also supports nsIProxiedProtocolHandler.
+ *
+ * @see nsIProxiedProtocolHandler::newProxiedChannel
+ */
+ nsICancelable asyncResolve(in nsISupports aChannelOrURI, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance from
+ * the given parameters. This method may be useful in conjunction with
+ * nsISocketTransportService::createTransport for creating, for example,
+ * a SOCKS connection.
+ *
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "http" - specifies a HTTP proxy
+ * "https" - specifies HTTP proxying over TLS connection to proxy
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * "direct" - specifies a direct connection (useful for failover)
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost,
+ in long aPort, in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance for
+ * with the specified username and password.
+ * Currently implemented for SOCKS proxies only.
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aUsername
+ * The proxy username
+ * @param aPassword
+ * The proxy password
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in ACString aUsername, in ACString aPassword,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * If the proxy identified by aProxyInfo is unavailable for some reason,
+ * this method may be called to access an alternate proxy that may be used
+ * instead. As a side-effect, this method may affect future result values
+ * from resolve/asyncResolve as well as from getFailoverForProxy.
+ *
+ * @param aProxyInfo
+ * The proxy that was unavailable.
+ * @param aURI
+ * The URI that was originally passed to resolve/asyncResolve.
+ * @param aReason
+ * The error code corresponding to the proxy failure. This value
+ * may be used to tune the delay before this proxy is used again.
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if there is no alternate proxy available.
+ */
+ nsIProxyInfo getFailoverForProxy(in nsIProxyInfo aProxyInfo,
+ in nsIURI aURI,
+ in nsresult aReason);
+
+ /**
+ * This method may be used to register a proxy filter instance. Each proxy
+ * filter is registered with an associated position that determines the
+ * order in which the filters are applied (starting from position 0). When
+ * resolve/asyncResolve is called, it generates a list of proxies for the
+ * given URI, and then it applies the proxy filters. The filters have the
+ * opportunity to modify the list of proxies.
+ *
+ * If two filters register for the same position, then the filters will be
+ * visited in the order in which they were registered.
+ *
+ * If the filter is already registered, then its position will be updated.
+ *
+ * After filters have been run, any disabled or disallowed proxies will be
+ * removed from the list. A proxy is disabled if it had previously failed-
+ * over to another proxy (see getFailoverForProxy). A proxy is disallowed,
+ * for example, if it is a HTTP proxy and the nsIProtocolHandler for the
+ * queried URI does not permit proxying via HTTP.
+ *
+ * If a nsIProtocolHandler disallows all proxying, then filters will never
+ * have a chance to intercept proxy requests for such URLs.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ *
+ * NOTE: It is possible to construct filters that compete with one another
+ * in undesirable ways. This API does not attempt to protect against such
+ * problems. It is recommended that any extensions that choose to call
+ * this method make their position value configurable at runtime (perhaps
+ * via the preferences service).
+ */
+ void registerFilter(in nsIProtocolProxyFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * Similar to registerFilter, but accepts an nsIProtocolProxyChannelFilter,
+ * which selects proxies according to channel rather than URI.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ */
+ void registerChannelFilter(in nsIProtocolProxyChannelFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * This method may be used to unregister a proxy filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be unregistered.
+ */
+ void unregisterFilter(in nsIProtocolProxyFilter aFilter);
+
+ /**
+ * This method may be used to unregister a proxy channel filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be unregistered.
+ */
+ void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter);
+
+ /**
+ * These values correspond to the possible integer values for the
+ * network.proxy.type preference.
+ */
+ const unsigned long PROXYCONFIG_DIRECT = 0;
+ const unsigned long PROXYCONFIG_MANUAL = 1;
+ const unsigned long PROXYCONFIG_PAC = 2;
+ const unsigned long PROXYCONFIG_WPAD = 4;
+ const unsigned long PROXYCONFIG_SYSTEM = 5;
+
+ /**
+ * This attribute specifies the current type of proxy configuration.
+ */
+ readonly attribute unsigned long proxyConfigType;
+};
diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl
new file mode 100644
index 000000000..6cd125e58
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService2.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolProxyService.idl"
+
+/**
+ * An extension of nsIProtocolProxyService
+ */
+[scriptable, uuid(b2e5b2c0-e21e-4845-b336-be6d60a38951)]
+interface nsIProtocolProxyService2 : nsIProtocolProxyService
+{
+ /**
+ * Call this method to cause the PAC file (if any is configured) to be
+ * reloaded. The PAC file is loaded asynchronously.
+ */
+ void reloadPAC();
+
+ /**
+ * This method is identical to asyncResolve() except:
+ * - it only accepts an nsIChannel, not an nsIURI;
+ * - it may execute the callback function immediately (i.e from the stack
+ * of asyncResolve2()) if it is immediately ready to run.
+ * The nsICancelable return value will be null in that case.
+ */
+ nsICancelable asyncResolve2(in nsIChannel aChannel, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback);
+};
diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl
new file mode 100644
index 000000000..69fc34650
--- /dev/null
+++ b/netwerk/base/nsIProxiedChannel.idl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProxyInfo;
+
+/**
+ * An interface for accessing the proxy info that a channel was
+ * constructed with.
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+[scriptable, uuid(6238f134-8c3f-4354-958f-dfd9d54a4446)]
+interface nsIProxiedChannel : nsISupports
+{
+ /**
+ * Gets the proxy info the channel was constructed with. null or a
+ * proxyInfo with type "direct" mean no proxy.
+ *
+ * The returned proxy info must not be modified.
+ */
+ readonly attribute nsIProxyInfo proxyInfo;
+};
+
+
diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl
new file mode 100644
index 000000000..604177c12
--- /dev/null
+++ b/netwerk/base/nsIProxiedProtocolHandler.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIChannel;
+interface nsIURI;
+interface nsIProxyInfo;
+interface nsILoadInfo;
+
+[scriptable, uuid(3756047a-fa2b-4b45-9948-3b5f8fc375e7)]
+interface nsIProxiedProtocolHandler : nsIProtocolHandler
+{
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ * @param aLoadInfo used to evaluate who initated the resource request.
+ */
+ nsIChannel newProxiedChannel2(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI,
+ in nsILoadInfo aLoadInfo);
+
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ */
+ nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI);
+};
diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl
new file mode 100644
index 000000000..46ab438b2
--- /dev/null
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface identifies a proxy server.
+ */
+[scriptable, uuid(63fff172-2564-4138-96c6-3ae7d245fbed)]
+interface nsIProxyInfo : nsISupports
+{
+ /**
+ * This attribute specifies the hostname of the proxy server.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * This attribute specifies the port number of the proxy server.
+ */
+ readonly attribute long port;
+
+ /**
+ * This attribute specifies the type of the proxy server as an ASCII string.
+ *
+ * Some special values for this attribute include (but are not limited to)
+ * the following:
+ * "http" HTTP proxy (or SSL CONNECT for HTTPS)
+ * "https" HTTP proxying over TLS connection to proxy
+ * "socks" SOCKS v5 proxy
+ * "socks4" SOCKS v4 proxy
+ * "direct" no proxy
+ * "unknown" unknown proxy (see nsIProtocolProxyService::resolve)
+ *
+ * A future version of this interface may define additional types.
+ */
+ readonly attribute ACString type;
+
+ /**
+ * This attribute specifies flags that modify the proxy type. The value of
+ * this attribute is the bit-wise combination of the Proxy Flags defined
+ * below. Any undefined bits are reserved for future use.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * This attribute specifies flags that were used by nsIProxyProtocolService when
+ * creating this ProxyInfo element.
+ */
+ readonly attribute unsigned long resolveFlags;
+
+ /**
+ * Specifies a proxy username.
+ */
+ readonly attribute ACString username;
+
+ /**
+ * Specifies a proxy password.
+ */
+ readonly attribute ACString password;
+
+ /**
+ * This attribute specifies the failover timeout in seconds for this proxy.
+ * If a nsIProxyInfo is reported as failed via nsIProtocolProxyService::
+ * getFailoverForProxy, then the failed proxy will not be used again for this
+ * many seconds.
+ */
+ readonly attribute unsigned long failoverTimeout;
+
+ /**
+ * This attribute specifies the proxy to failover to when this proxy fails.
+ */
+ attribute nsIProxyInfo failoverProxy;
+
+
+ /****************************************************************************
+ * The following "Proxy Flags" may be bit-wise combined to construct the
+ * flags attribute defined on this interface. All unspecified bits are
+ * reserved for future use.
+ */
+
+ /**
+ * This flag is set if the proxy is to perform name resolution itself. If
+ * this is the case, the hostname is used in some fashion, and we shouldn't
+ * do any form of DNS lookup ourselves.
+ */
+ const unsigned short TRANSPARENT_PROXY_RESOLVES_HOST = 1 << 0;
+};
diff --git a/netwerk/base/nsIRandomGenerator.idl b/netwerk/base/nsIRandomGenerator.idl
new file mode 100644
index 000000000..29ee25bbe
--- /dev/null
+++ b/netwerk/base/nsIRandomGenerator.idl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface used to generate random data.
+ *
+ * @threadsafe
+ */
+[scriptable, uuid(2362d97a-747a-4576-8863-697667309209)]
+interface nsIRandomGenerator : nsISupports {
+ /**
+ * Generates the specified amount of random bytes.
+ *
+ * @param aLength
+ * The length of the data to generate.
+ * @param aBuffer
+ * A buffer that contains random bytes of size aLength.
+ */
+ void generateRandomBytes(in unsigned long aLength,
+ [retval, array, size_is(aLength)] out octet aBuffer);
+};
diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl
new file mode 100644
index 000000000..c5cce0b1c
--- /dev/null
+++ b/netwerk/base/nsIRedirectChannelRegistrar.idl
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIParentChannel;
+
+/**
+ * Used on the chrome process as a service to join channel implementation
+ * and parent IPC protocol side under a unique id. Provides this way a generic
+ * communication while redirecting to various protocols.
+ *
+ * See also nsIChildChannel and nsIParentChannel.
+ */
+
+[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)]
+interface nsIRedirectChannelRegistrar : nsISupports
+{
+ /**
+ * Register the redirect target channel and obtain a unique ID for that
+ * channel.
+ *
+ * Primarily used in HttpChannelParentListener::AsyncOnChannelRedirect to get
+ * a channel id sent to the HttpChannelChild being redirected.
+ */
+ uint32_t registerChannel(in nsIChannel channel);
+
+ /**
+ * First, search for the channel registered under the id. If found return
+ * it. Then, register under the same id the parent side of IPC protocol
+ * to let it be later grabbed back by the originator of the redirect and
+ * notifications from the real channel could be forwarded to this parent
+ * channel.
+ *
+ * Primarily used in parent side of an IPC protocol implementation
+ * in reaction to nsIChildChannel.connectParent(id) called from the child
+ * process.
+ */
+ nsIChannel linkChannels(in uint32_t id, in nsIParentChannel channel);
+
+ /**
+ * Returns back the channel previously registered under the ID with
+ * registerChannel method.
+ *
+ * Primarilly used in chrome IPC side of protocols when attaching a redirect
+ * target channel to an existing 'real' channel implementation.
+ */
+ nsIChannel getRegisteredChannel(in uint32_t id);
+
+ /**
+ * Returns the stream listener that shall be attached to the redirect target
+ * channel, all notification from the redirect target channel will be
+ * forwarded to this stream listener.
+ *
+ * Primarilly used in HttpChannelParentListener::OnRedirectResult callback
+ * to grab the created parent side of the channel and forward notifications
+ * to it.
+ */
+ nsIParentChannel getParentChannel(in uint32_t id);
+
+ /**
+ * To not force all channel implementations to support weak reference
+ * consumers of this service must ensure release of registered channels them
+ * self. This releases both the real and parent channel registered under
+ * the id.
+ *
+ * Primarilly used in HttpChannelParentListener::OnRedirectResult callback.
+ */
+ void deregisterChannels(in uint32_t id);
+};
diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl
new file mode 100644
index 000000000..1afb3e9ec
--- /dev/null
+++ b/netwerk/base/nsIRedirectResultListener.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(85cd2640-e91e-41ac-bdca-1dbf10dc131e)]
+interface nsIRedirectResultListener : nsISupports
+{
+ /**
+ * When an HTTP redirect has been processed (either successfully or not)
+ * nsIHttpChannel will call this function if its callbacks implement this
+ * interface.
+ *
+ * @param proceeding
+ * Indicated whether the redirect will be proceeding, or not (i.e.
+ * has been canceled, or failed).
+ */
+ void onRedirectResult(in boolean proceeding);
+};
diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl
new file mode 100644
index 000000000..544612152
--- /dev/null
+++ b/netwerk/base/nsIRequest.idl
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+typedef unsigned long nsLoadFlags;
+
+/**
+ * nsIRequest
+ */
+[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)]
+interface nsIRequest : nsISupports
+{
+ /**
+ * The name of the request. Often this is the URI of the request.
+ */
+ readonly attribute AUTF8String name;
+
+ /**
+ * Indicates whether the request is pending. nsIRequest::isPending is
+ * true when there is an outstanding asynchronous event that will make
+ * the request no longer be pending. Requests do not necessarily start
+ * out pending; in some cases, requests have to be explicitly initiated
+ * (e.g. nsIChannel implementations are only pending once asyncOpen
+ * returns successfully).
+ *
+ * Requests can become pending multiple times during their lifetime.
+ *
+ * @return TRUE if the request has yet to reach completion.
+ * @return FALSE if the request has reached completion (e.g., after
+ * OnStopRequest has fired).
+ * @note Suspended requests are still considered pending.
+ */
+ boolean isPending();
+
+ /**
+ * The error status associated with the request.
+ */
+ readonly attribute nsresult status;
+
+ /**
+ * Cancels the current request. This will close any open input or
+ * output streams and terminate any async requests. Users should
+ * normally pass NS_BINDING_ABORTED, although other errors may also
+ * be passed. The error passed in will become the value of the
+ * status attribute.
+ *
+ * Implementations must not send any notifications (e.g. via
+ * nsIRequestObserver) synchronously from this function. Similarly,
+ * removal from the load group (if any) must also happen asynchronously.
+ *
+ * Requests that use nsIStreamListener must not call onDataAvailable
+ * anymore after cancel has been called.
+ *
+ * @param aStatus the reason for canceling this request.
+ *
+ * NOTE: most nsIRequest implementations expect aStatus to be a
+ * failure code; however, some implementations may allow aStatus to
+ * be a success code such as NS_OK. In general, aStatus should be
+ * a failure code.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+ void suspend();
+
+ /**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+ void resume();
+
+ /**
+ * The load group of this request. While pending, the request is a
+ * member of the load group. It is the responsibility of the request
+ * to implement this policy.
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load flags of this request. Bits 0-15 are reserved.
+ *
+ * When added to a load group, this request's load flags are merged with
+ * the load flags of the load group.
+ */
+ attribute nsLoadFlags loadFlags;
+
+ /**
+ * Mask defining the bits reserved for nsIRequest LoadFlags
+ */
+ const unsigned long LOAD_REQUESTMASK = 0xFFFF;
+
+ /**************************************************************************
+ * Listed below are the various load flags which may be or'd together.
+ */
+
+ /**
+ * No special load flags:
+ */
+ const unsigned long LOAD_NORMAL = 0;
+
+ /**
+ * Do not deliver status notifications to the nsIProgressEventSink and
+ * do not block the loadgroup from completing (should this load belong to one).
+ * Note: Progress notifications will still be delivered.
+ */
+ const unsigned long LOAD_BACKGROUND = 1 << 0;
+
+ /**************************************************************************
+ * The following flags control the flow of data into the cache.
+ */
+
+ /**
+ * This flag prevents loading of the request with an HTTP pipeline.
+ * Generally this is because the resource is expected to take a
+ * while to load and may cause head of line blocking problems.
+ */
+ const unsigned long INHIBIT_PIPELINE = 1 << 6;
+
+ /**
+ * This flag prevents caching of any kind. It does not, however, prevent
+ * cached content from being used to satisfy this request.
+ */
+ const unsigned long INHIBIT_CACHING = 1 << 7;
+
+ /**
+ * This flag prevents caching on disk (or other persistent media), which
+ * may be needed to preserve privacy.
+ */
+ const unsigned long INHIBIT_PERSISTENT_CACHING = 1 << 8;
+
+ /**************************************************************************
+ * The following flags control what happens when the cache contains data
+ * that could perhaps satisfy this request. They are listed in descending
+ * order of precidence.
+ */
+
+ /**
+ * Force an end-to-end download of content data from the origin server.
+ * This flag is used for a shift-reload.
+ */
+ const unsigned long LOAD_BYPASS_CACHE = 1 << 9;
+
+ /**
+ * Attempt to force a load from the cache, bypassing ALL validation logic
+ * (note: this is stronger than VALIDATE_NEVER, which still validates for
+ * certain conditions).
+ *
+ * If the resource is not present in cache, it will be loaded from the
+ * network. Combine this flag with LOAD_ONLY_FROM_CACHE if you wish to
+ * perform cache-only loads without validation checks.
+ *
+ * This flag is used when browsing via history. It is not recommended for
+ * normal browsing as it may likely violate reasonable assumptions made by
+ * the server and confuse users.
+ */
+ const unsigned long LOAD_FROM_CACHE = 1 << 10;
+
+ /**
+ * The following flags control the frequency of cached content validation
+ * when neither LOAD_BYPASS_CACHE or LOAD_FROM_CACHE are set. By default,
+ * cached content is automatically validated if necessary before reuse.
+ *
+ * VALIDATE_ALWAYS forces validation of any cached content independent of
+ * its expiration time (unless it is https with Cache-Control: immutable)
+ *
+ * VALIDATE_NEVER disables validation of cached content, unless it arrived
+ * with the "Cache: no-store" header, or arrived via HTTPS with the
+ * "Cache: no-cache" header.
+ *
+ * VALIDATE_ONCE_PER_SESSION disables validation of expired content,
+ * provided it has already been validated (at least once) since the start
+ * of this session.
+ *
+ * NOTE TO IMPLEMENTORS:
+ * These flags are intended for normal browsing, and they should therefore
+ * not apply to content that must be validated before each use. Consider,
+ * for example, a HTTP response with a "Cache-control: no-cache" header.
+ * According to RFC2616, this response must be validated before it can
+ * be taken from a cache. Breaking this requirement could result in
+ * incorrect and potentially undesirable side-effects.
+ */
+ const unsigned long VALIDATE_ALWAYS = 1 << 11;
+ const unsigned long VALIDATE_NEVER = 1 << 12;
+ const unsigned long VALIDATE_ONCE_PER_SESSION = 1 << 13;
+
+ /**
+ * When set, this flag indicates that no user-specific data should be added
+ * to the request when opened. This means that things like authorization
+ * tokens or cookie headers should not be added.
+ */
+ const unsigned long LOAD_ANONYMOUS = 1 << 14;
+
+ /**
+ * When set, this flag indicates that caches of network connections,
+ * particularly HTTP persistent connections, should not be used.
+ */
+ const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
+};
diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl
new file mode 100644
index 000000000..b40ba7d18
--- /dev/null
+++ b/netwerk/base/nsIRequestContext.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+// Forward-declare mozilla::net::SpdyPushCache
+namespace mozilla {
+namespace net {
+class SpdyPushCache;
+}
+}
+%}
+
+[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache);
+
+/**
+ * The nsIRequestContext is used to maintain state about connections
+ * that are in some way associated with each other (often by being part
+ * of the same load group) and how they interact with blocking items like
+ * HEAD css/js loads.
+ *
+ * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext.
+ */
+[scriptable, uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)]
+interface nsIRequestContext : nsISupports
+{
+ /**
+ * A unique identifier for this request context
+ */
+ [noscript] readonly attribute nsID ID;
+
+ /**
+ * Number of active blocking transactions associated with this context
+ */
+ readonly attribute unsigned long blockingTransactionCount;
+
+ /**
+ * Increase the number of active blocking transactions associated
+ * with this context by one.
+ */
+ void addBlockingTransaction();
+
+ /**
+ * Decrease the number of active blocking transactions associated
+ * with this context by one. The return value is the number of remaining
+ * blockers.
+ */
+ unsigned long removeBlockingTransaction();
+
+ /**
+ * This gives out a weak pointer to the push cache.
+ * The nsIRequestContext implementation owns the cache
+ * and will destroy it when overwritten or when the context
+ * ends.
+ */
+ [noscript] attribute SpdyPushCachePtr spdyPushCache;
+
+ /**
+ * This holds a cached value of the user agent override.
+ */
+ [noscript] attribute ACString userAgentOverride;
+};
+
+/**
+ * The nsIRequestContextService is how anyone gets access to a request
+ * context when they haven't been explicitly given a strong reference to an
+ * existing one. It is responsible for creating and handing out strong
+ * references to nsIRequestContexts, but only keeps weak references itself.
+ * The shared request context will go away once no one else is keeping a
+ * reference to it. If you ask for a request context that has no one else
+ * holding a reference to it, you'll get a brand new request context. Anyone
+ * who asks for the same request context while you're holding a reference
+ * will get a reference to the same request context you have.
+ */
+[uuid(7fcbf4da-d828-4acc-b144-e5435198f727)]
+interface nsIRequestContextService : nsISupports
+{
+ /**
+ * Get an existing request context from its ID
+ */
+ nsIRequestContext getRequestContext(in nsIDRef id);
+
+ /**
+ * Create a new request context identifier
+ */
+ nsID newRequestContextID();
+
+ /**
+ * Remove an existing request context from its ID
+ */
+ void removeRequestContext(in nsIDRef id);
+};
diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl
new file mode 100644
index 000000000..5ab94001f
--- /dev/null
+++ b/netwerk/base/nsIRequestObserver.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * nsIRequestObserver
+ */
+[scriptable, uuid(fd91e2e0-1481-11d3-9333-00104ba0fd40)]
+interface nsIRequestObserver : nsISupports
+{
+ /**
+ * Called to signify the beginning of an asynchronous request.
+ *
+ * @param aRequest request being observed
+ * @param aContext user defined context
+ *
+ * An exception thrown from onStartRequest has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onStartRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Called to signify the end of an asynchronous request. This
+ * call is always preceded by a call to onStartRequest.
+ *
+ * @param aRequest request being observed
+ * @param aContext user defined context
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ *
+ * An exception thrown from onStopRequest is generally ignored.
+ */
+ void onStopRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl
new file mode 100644
index 000000000..7b79f5342
--- /dev/null
+++ b/netwerk/base/nsIRequestObserverProxy.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIEventTarget;
+
+/**
+ * A request observer proxy is used to ship data over to another thread
+ * specified by the thread's dispatch target. The "true" request observer's
+ * methods are invoked on the other thread.
+ *
+ * This interface only provides the initialization needed after construction.
+ * Otherwise, these objects are used simply as nsIRequestObserver's.
+ */
+[scriptable, uuid(c2b06151-1bf8-4eef-aea9-1532f12f5a10)]
+interface nsIRequestObserverProxy : nsIRequestObserver
+{
+ /**
+ * Initializes an nsIRequestObserverProxy.
+ *
+ * @param observer - receives observer notifications on the main thread
+ * @param context - the context argument that will be passed to OnStopRequest
+ * and OnStartRequest. This has to be stored permanently on
+ * initialization because it sometimes can't be
+ * AddRef/Release'd off-main-thread.
+ */
+ void init(in nsIRequestObserver observer, in nsISupports context);
+};
diff --git a/netwerk/base/nsIResumableChannel.idl b/netwerk/base/nsIResumableChannel.idl
new file mode 100644
index 000000000..c58c14cc7
--- /dev/null
+++ b/netwerk/base/nsIResumableChannel.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+[scriptable, uuid(4ad136fa-83af-4a22-a76e-503642c0f4a8)]
+interface nsIResumableChannel : nsISupports {
+ /**
+ * Prepare this channel for resuming. The request will not start until
+ * asyncOpen or open is called. Calling resumeAt after open or asyncOpen
+ * has been called has undefined behaviour.
+ *
+ * @param startPos the starting offset, in bytes, to use to download
+ * @param entityID information about the file, to match before obtaining
+ * the file. Pass an empty string to use anything.
+ *
+ * During OnStartRequest, this channel will have a status of
+ * NS_ERROR_NOT_RESUMABLE if the file cannot be resumed, eg because the
+ * server doesn't support this. This error may occur even if startPos
+ * is 0, so that the front end can warn the user.
+ * Similarly, the status of this channel during OnStartRequest may be
+ * NS_ERROR_ENTITY_CHANGED, which indicates that the entity has changed,
+ * as indicated by a changed entityID.
+ * In both of these cases, no OnDataAvailable will be called, and
+ * OnStopRequest will immediately follow with the same status code.
+ */
+ void resumeAt(in unsigned long long startPos,
+ in ACString entityID);
+
+ /**
+ * The entity id for this URI. Available after OnStartRequest.
+ * @throw NS_ERROR_NOT_RESUMABLE if this load is not resumable.
+ */
+ readonly attribute ACString entityID;
+};
diff --git a/netwerk/base/nsISecCheckWrapChannel.idl b/netwerk/base/nsISecCheckWrapChannel.idl
new file mode 100644
index 000000000..21f4d0c29
--- /dev/null
+++ b/netwerk/base/nsISecCheckWrapChannel.idl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * nsISecCheckWrapChannel
+ * Describes an XPCOM component used to wrap channels for performing
+ * security checks. Channels wrapped inside this class can use
+ * this interface to query the wrapped inner channel.
+ */
+
+[scriptable, uuid(9446c5d5-c9fb-4a6e-acf9-ca4fc666efe0)]
+interface nsISecCheckWrapChannel : nsISupports
+{
+ /**
+ * Returns the wrapped channel inside this class.
+ */
+ readonly attribute nsIChannel innerChannel;
+
+};
diff --git a/netwerk/base/nsISecureBrowserUI.idl b/netwerk/base/nsISecureBrowserUI.idl
new file mode 100644
index 000000000..396aa42f8
--- /dev/null
+++ b/netwerk/base/nsISecureBrowserUI.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDOMElement;
+interface nsIDocShell;
+
+[scriptable, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)]
+interface nsISecureBrowserUI : nsISupports
+{
+ void init(in mozIDOMWindowProxy window);
+ void setDocShell(in nsIDocShell docShell);
+
+ readonly attribute unsigned long state;
+};
+
+%{C++
+#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1"
+%}
diff --git a/netwerk/base/nsISecurityEventSink.idl b/netwerk/base/nsISecurityEventSink.idl
new file mode 100644
index 000000000..568753023
--- /dev/null
+++ b/netwerk/base/nsISecurityEventSink.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+
+[scriptable, uuid(a71aee68-dd38-4736-bd79-035fea1a1ec6)]
+interface nsISecurityEventSink : nsISupports
+{
+
+ /**
+ * Fired when a security change occurs due to page transitions,
+ * or end document load. This interface should be called by
+ * a security package (eg Netscape Personal Security Manager)
+ * to notify nsIWebProgressListeners that security state has
+ * changed. State flags are in nsIWebProgressListener.idl
+ */
+
+ void onSecurityChange(in nsISupports i_Context, in unsigned long state);
+};
+
+
+
+
diff --git a/netwerk/base/nsISecurityInfoProvider.idl b/netwerk/base/nsISecurityInfoProvider.idl
new file mode 100644
index 000000000..0cf83e69c
--- /dev/null
+++ b/netwerk/base/nsISecurityInfoProvider.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b8cc9126-9319-4415-afd9-b82220d453ed)]
+interface nsISecurityInfoProvider : nsISupports
+{
+ /**
+ * The security info for this provider, if any.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * Whether this provider has transferred data. If it hasn't, its
+ * security info should be ignored.
+ */
+ readonly attribute boolean hasTransferredData;
+};
diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl
new file mode 100644
index 000000000..abb3f082b
--- /dev/null
+++ b/netwerk/base/nsISensitiveInfoHiddenURI.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a5761968-6e1a-4f2d-8191-ec749602b178)]
+interface nsISensitiveInfoHiddenURI : nsISupports
+{
+ /**
+ * Returns the spec attribute with sensitive information hidden. This will
+ * only affect uri with password. The password part of uri will be
+ * transformed into "****".
+ */
+ AUTF8String getSensitiveInfoHiddenSpec();
+};
diff --git a/netwerk/base/nsISerializationHelper.idl b/netwerk/base/nsISerializationHelper.idl
new file mode 100644
index 000000000..740927f40
--- /dev/null
+++ b/netwerk/base/nsISerializationHelper.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Simple scriptable serialization helper. Can be used as a service.
+ */
+
+interface nsISerializable;
+
+[scriptable, uuid(31654c0f-35f3-44c6-b31e-37a11516e6bc)]
+interface nsISerializationHelper : nsISupports
+{
+ /**
+ * Serialize the object to a base64 string. This string can be later passed
+ * as an input to deserializeObject method.
+ */
+ ACString serializeToString(in nsISerializable serializable);
+
+ /**
+ * Takes base64 encoded string that cointains serialization of a single
+ * object. Most commonly, input is result of previous call to
+ * serializeToString.
+ */
+ nsISupports deserializeObject(in ACString input);
+};
diff --git a/netwerk/base/nsIServerSocket.idl b/netwerk/base/nsIServerSocket.idl
new file mode 100644
index 000000000..fa54104c3
--- /dev/null
+++ b/netwerk/base/nsIServerSocket.idl
@@ -0,0 +1,237 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIServerSocketListener;
+interface nsISocketTransport;
+
+native PRNetAddr(union PRNetAddr);
+[ptr] native PRNetAddrPtr(union PRNetAddr);
+
+typedef unsigned long nsServerSocketFlag;
+
+/**
+ * nsIServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections.
+ */
+[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)]
+interface nsIServerSocket : nsISupports
+{
+ /**
+ * @name Server Socket Flags
+ * These flags define various socket options.
+ * @{
+ */
+ /// The server socket will only respond to connections on the
+ /// local loopback interface. Otherwise, it will accept connections
+ /// from any interface. To specify a particular network interface,
+ /// use initWithAddress.
+ const nsServerSocketFlag LoopbackOnly = 0x00000001;
+ /// The server socket will not be closed when Gecko is set
+ /// offline.
+ const nsServerSocketFlag KeepWhenOffline = 0x00000002;
+ /** @} */
+
+ /**
+ * init
+ *
+ * This method initializes a server socket.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the server socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * initSpecialConnection
+ *
+ * This method initializes a server socket and offers the ability to have
+ * that socket not get terminated if Gecko is set offline.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aFlags
+ * Flags for the socket.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void initSpecialConnection(in long aPort,
+ in nsServerSocketFlag aFlags,
+ in long aBackLog);
+
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a server socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this server socket should be bound.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog);
+
+ /**
+ * initWithFilename
+ *
+ * This method initializes a Unix domain or "local" server socket. Such
+ * a socket has a name in the filesystem, like an ordinary file. To
+ * connect, a client supplies the socket's filename, and the usual
+ * permission checks on socket apply.
+ *
+ * This makes Unix domain sockets useful for communication between the
+ * programs being run by a specific user on a single machine: the
+ * operating system takes care of authentication, and the user's home
+ * directory or profile directory provide natural per-user rendezvous
+ * points.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * All components of the path prefix of |aPath| must name directories;
+ * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY.
+ *
+ * This call requires execute permission on all directories containing
+ * the one in which the socket is to be created, and write and execute
+ * permission on the directory itself. Otherwise, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * This call creates the socket's directory entry. There must not be
+ * any existing entry with the given name. If there is, this returns
+ * NS_ERROR_SOCKET_ADDRESS_IN_USE.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aPath nsIFile
+ * The file name at which the socket should be created.
+ *
+ * @param aPermissions unsigned long
+ * Unix-style permission bits to be applied to the new socket.
+ *
+ * Note about permissions: Linux's unix(7) man page claims that some
+ * BSD-derived systems ignore permissions on UNIX-domain sockets;
+ * NetBSD's bind(2) man page agrees, but says it does check now (dated
+ * 2005). POSIX has required 'connect' to fail if write permission on
+ * the socket itself is not granted since 2003 (Issue 6). NetBSD says
+ * that the permissions on the containing directory (execute) have
+ * always applied, so creating sockets in appropriately protected
+ * directories should be secure on both old and new systems.
+ */
+ void initWithFilename(in nsIFile aPath, in unsigned long aPermissions,
+ in long aBacklog);
+
+ /**
+ * close
+ *
+ * This method closes a server socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this server socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the server socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIServerSocketListener aListener);
+
+ /**
+ * Returns the port of this server socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this server socket is bound. Since a
+ * server socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a server socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] PRNetAddr getAddress();
+};
+
+/**
+ * nsIServerSocketListener
+ *
+ * This interface is notified whenever a server socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(836d98ec-fee2-4bde-b609-abd5e966eabd)]
+interface nsIServerSocketListener : nsISupports
+{
+ /**
+ * onSocketAccepted
+ *
+ * This method is called when a client connection is accepted.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aTransport
+ * The connected socket transport.
+ */
+ void onSocketAccepted(in nsIServerSocket aServ,
+ in nsISocketTransport aTransport);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The server socket is effectively dead after this notification.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aStatus
+ * The reason why the server socket stopped listening. If the
+ * server socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIServerSocket aServ, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsISimpleStreamListener.idl b/netwerk/base/nsISimpleStreamListener.idl
new file mode 100644
index 000000000..99169481f
--- /dev/null
+++ b/netwerk/base/nsISimpleStreamListener.idl
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIOutputStream;
+
+/**
+ * A simple stream listener can be used with AsyncRead to supply data to
+ * a output stream.
+ */
+[scriptable, uuid(a9b84f6a-0824-4278-bae6-bfca0570a26e)]
+interface nsISimpleStreamListener : nsIStreamListener
+{
+ /**
+ * Initialize the simple stream listener.
+ *
+ * @param aSink data will be read from the channel to this output stream.
+ * Must implement writeFrom.
+ * @param aObserver optional stream observer (can be NULL)
+ */
+ void init(in nsIOutputStream aSink,
+ in nsIRequestObserver aObserver);
+};
diff --git a/netwerk/base/nsISocketFilter.idl b/netwerk/base/nsISocketFilter.idl
new file mode 100644
index 000000000..0846fa2ed
--- /dev/null
+++ b/netwerk/base/nsISocketFilter.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsINetAddr.idl"
+
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+
+
+/**
+ * Filters are created and run on the parent, and filter all packets, both
+ * ingoing and outgoing. The child must specify the name of a recognized filter
+ * in order to create a socket.
+ */
+[uuid(afe2c40c-b9b9-4207-b898-e5cde18c6139)]
+interface nsISocketFilter : nsISupports
+{
+ const long SF_INCOMING = 0;
+ const long SF_OUTGOING = 1;
+
+ bool filterPacket([const]in NetAddrPtr remote_addr,
+ [const, array, size_is(len)]in uint8_t data,
+ in unsigned long len,
+ in long direction);
+};
+
+/**
+ * Factory of a specified filter.
+ */
+[uuid(81ee76c6-4753-4125-9c8c-290ed9ba62fb)]
+interface nsISocketFilterHandler : nsISupports
+{
+ nsISocketFilter newFilter();
+};
+
+%{C++
+/**
+ * Filter handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/udp-filter-handler;1?name="
+#define NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/tcp-filter-handler;1?name="
+
+#define NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX "stun"
+
+#define NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+
+
+#define NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+%}
diff --git a/netwerk/base/nsISocketTransport.idl b/netwerk/base/nsISocketTransport.idl
new file mode 100644
index 000000000..6395d6b5f
--- /dev/null
+++ b/netwerk/base/nsISocketTransport.idl
@@ -0,0 +1,256 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITransport.idl"
+
+interface nsIInterfaceRequestor;
+interface nsINetAddr;
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native NeckoOriginAttributes(mozilla::NeckoOriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes);
+
+/**
+ * nsISocketTransport
+ *
+ * NOTE: Connection setup is triggered by opening an input or output stream,
+ * it does not start on its own. Completion of the connection setup is
+ * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set).
+ *
+ * NOTE: This is a free-threaded interface, meaning that the methods on
+ * this interface may be called from any thread.
+ */
+[scriptable, uuid(79221831-85e2-43a8-8152-05d77d6fde31)]
+interface nsISocketTransport : nsITransport
+{
+ /**
+ * Get the peer's host for the underlying socket connection.
+ * For Unix domain sockets, this is a pathname, or the empty string for
+ * unnamed and abstract socket addresses.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * Get the port for the underlying socket connection.
+ * For Unix domain sockets, this is zero.
+ */
+ readonly attribute long port;
+
+ /**
+ * The origin attributes are used to create sockets. The first party domain
+ * will eventually be used to isolate OCSP cache and is only non-empty when
+ * "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ * carry origin attributes down to NSPR layers which are final consumers.
+ * It must be set before the socket transport is built.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ NeckoOriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+ /**
+ * The platform-specific network interface id that this socket
+ * associated with. Note that this attribute can be only accessed
+ * in the socket thread.
+ */
+ attribute ACString networkInterfaceId;
+
+ /**
+ * Returns the IP address of the socket connection peer. This
+ * attribute is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getPeerAddr();
+
+ /**
+ * Returns the IP address of the initiating end. This attribute
+ * is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getSelfAddr();
+
+ /**
+ * Bind to a specific local address.
+ */
+ [noscript] void bind(in NetAddrPtr aLocalAddr);
+
+ /**
+ * Returns a scriptable version of getPeerAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptablePeerAddr();
+
+ /**
+ * Returns a scriptable version of getSelfAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptableSelfAddr();
+
+ /**
+ * Security info object returned from the secure socket provider. This
+ * object supports nsISSLSocketControl, nsITransportSecurityInfo, and
+ * possibly other interfaces.
+ *
+ * This attribute is only available once the socket is connected.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * Security notification callbacks passed to the secure socket provider
+ * via nsISSLSocketControl at socket creation time.
+ *
+ * NOTE: this attribute cannot be changed once a stream has been opened.
+ */
+ attribute nsIInterfaceRequestor securityCallbacks;
+
+ /**
+ * Test if this socket transport is (still) connected.
+ */
+ boolean isAlive();
+
+ /**
+ * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX
+ * as aValue to setTimeout. The implementation may truncate timeout values
+ * to a smaller range of values (e.g., 0 to 0xFFFF).
+ */
+ unsigned long getTimeout(in unsigned long aType);
+ void setTimeout(in unsigned long aType, in unsigned long aValue);
+
+ /**
+ * Values for the aType parameter passed to get/setTimeout.
+ */
+ const unsigned long TIMEOUT_CONNECT = 0;
+ const unsigned long TIMEOUT_READ_WRITE = 1;
+
+ /**
+ * nsITransportEventSink status codes.
+ *
+ * Although these look like XPCOM error codes and are passed in an nsresult
+ * variable, they are *not* error codes. Note that while they *do* overlap
+ * with existing error codes in Necko, these status codes are confined
+ * within a very limited context where no error codes may appear, so there
+ * is no ambiguity.
+ *
+ * The values of these status codes must never change.
+ *
+ * The status codes appear in near-chronological order (not in numeric
+ * order). STATUS_RESOLVING may be skipped if the host does not need to be
+ * resolved. STATUS_WAITING_FOR is an optional status code, which the impl
+ * of this interface may choose not to generate.
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_RESOLVING = 0x804b0003;
+ const unsigned long STATUS_RESOLVED = 0x804b000b;
+ const unsigned long STATUS_CONNECTING_TO = 0x804b0007;
+ const unsigned long STATUS_CONNECTED_TO = 0x804b0004;
+ const unsigned long STATUS_SENDING_TO = 0x804b0005;
+ const unsigned long STATUS_WAITING_FOR = 0x804b000a;
+ const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
+
+ /**
+ * connectionFlags is a bitmask that can be used to modify underlying
+ * behavior of the socket connection. See the flags below.
+ */
+ attribute unsigned long connectionFlags;
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When making a new connection BYPASS_CACHE will force the Necko DNS
+ * cache entry to be refreshed with a new call to NSPR if it is set before
+ * opening the new stream.
+ */
+ const unsigned long BYPASS_CACHE = (1 << 0);
+
+ /**
+ * When setting this flag, the socket will not apply any
+ * credentials when establishing a connection. For example,
+ * an SSL connection would not send any client-certificates
+ * if this flag is set.
+ */
+ const unsigned long ANONYMOUS_CONNECT = (1 << 1);
+
+ /**
+ * If set, we will skip all IPv6 addresses the host may have and only
+ * connect to IPv4 ones.
+ */
+ const unsigned long DISABLE_IPV6 = (1 << 2);
+
+ /**
+ * If set, indicates that the connection was initiated from a source
+ * defined as being private in the sense of Private Browsing. Generally,
+ * there should be no state shared between connections that are private
+ * and those that are not; it is OK for multiple private connections
+ * to share state with each other, and it is OK for multiple non-private
+ * connections to share state with each other.
+ */
+ const unsigned long NO_PERMANENT_STORAGE = (1 << 3);
+
+ /**
+ * If set, we will skip all IPv4 addresses the host may have and only
+ * connect to IPv6 ones.
+ */
+ const unsigned long DISABLE_IPV4 = (1 << 4);
+
+ /**
+ * If set, indicates that the socket should not connect if the hostname
+ * resolves to an RFC1918 address or IPv6 equivalent.
+ */
+ const unsigned long DISABLE_RFC1918 = (1 << 5);
+
+ /**
+ * This flag is an explicit opt-in that allows a normally secure socket
+ * provider to use, at its discretion, an insecure algorithm. e.g.
+ * a TLS socket without authentication.
+ */
+ const unsigned long MITM_OK = (1 << 6);
+
+ /**
+ * If set, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ const unsigned long BE_CONSERVATIVE = (1 << 7);
+
+ /**
+ * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
+ * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
+ * services require expedited-forwarding).
+ * Not setting this value will leave the socket with the default
+ * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
+ * IPTOS_PREC_ROUTINE).
+ */
+ attribute octet QoSBits;
+
+ /**
+ * TCP send and receive buffer sizes. A value of 0 means OS level
+ * auto-tuning is in effect.
+ */
+ attribute unsigned long recvBufferSize;
+ attribute unsigned long sendBufferSize;
+
+ /**
+ * TCP keepalive configuration (support varies by platform).
+ * Note that the attribute as well as the setter can only accessed
+ * in the socket thread.
+ */
+ attribute boolean keepaliveEnabled;
+ void setKeepaliveVals(in long keepaliveIdleTime,
+ in long keepaliveRetryInterval);
+};
diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl
new file mode 100644
index 000000000..06350b532
--- /dev/null
+++ b/netwerk/base/nsISocketTransportService.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsISocketTransport;
+interface nsIProxyInfo;
+interface nsIRunnable;
+
+%{C++
+class nsASocketHandler;
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+[ptr] native nsASocketHandlerPtr(nsASocketHandler);
+
+[scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)]
+interface nsISocketTransportService : nsISupports
+{
+ /**
+ * Creates a transport for a specified host and port.
+ *
+ * @param aSocketTypes
+ * array of socket type strings. null if using default socket type.
+ * @param aTypeCount
+ * specifies length of aSocketTypes.
+ * @param aHost
+ * specifies the target hostname or IP address literal of the peer
+ * for this socket.
+ * @param aPort
+ * specifies the target port of the peer for this socket.
+ * @param aProxyInfo
+ * specifies the transport-layer proxy type to use. null if no
+ * proxy. used for communicating information about proxies like
+ * SOCKS (which are transparent to upper protocols).
+ *
+ * @see nsIProxiedProtocolHandler
+ * @see nsIProtocolProxyService::GetProxyInfo
+ *
+ * NOTE: this function can be called from any thread
+ */
+ nsISocketTransport createTransport([array, size_is(aTypeCount)]
+ in string aSocketTypes,
+ in unsigned long aTypeCount,
+ in AUTF8String aHost,
+ in long aPort,
+ in nsIProxyInfo aProxyInfo);
+
+ /**
+ * Create a transport built on a Unix domain socket, connecting to the
+ * given filename.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * The |aPath| parameter must specify an existing directory entry.
+ * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND.
+ *
+ * The program must have search permission on all components of the
+ * path prefix of |aPath|, and read and write permission on |aPath|
+ * itself. Without such permission, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * The |aPath| parameter must refer to a unix-domain socket. Otherwise,
+ * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies
+ * ECONNREFUSED when "the target address was not listening for
+ * connections", and this is what Linux returns.)
+ *
+ * @param aPath
+ * The file name of the Unix domain socket to which we should
+ * connect.
+ */
+ nsISocketTransport createUnixDomainTransport(in nsIFile aPath);
+
+ /**
+ * Adds a new socket to the list of controlled sockets.
+ *
+ * This will fail with the error code NS_ERROR_NOT_AVAILABLE if the maximum
+ * number of sockets is already reached.
+ * In this case, the notifyWhenCanAttachSocket method should be used.
+ *
+ * @param aFd
+ * Open file descriptor of the socket to control.
+ * @param aHandler
+ * Socket handler that will receive notifications when the socket is
+ * ready or detached.
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void attachSocket(in PRFileDescPtr aFd,
+ in nsASocketHandlerPtr aHandler);
+
+ /**
+ * if the number of sockets reaches the limit, then consumers can be
+ * notified when the number of sockets becomes less than the limit. the
+ * notification is asynchronous, delivered via the given nsIRunnable
+ * instance on the socket transport thread.
+ *
+ * @param aEvent
+ * Event that will receive the notification when a new socket can
+ * be attached
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void notifyWhenCanAttachSocket(in nsIRunnable aEvent);
+};
+
+[scriptable, uuid(c5204623-5b58-4a16-8b2e-67c34dd02e3f)]
+interface nsIRoutedSocketTransportService : nsISocketTransportService
+{
+ // use this instead of createTransport when you have a transport
+ // that distinguishes between origin and route (aka connection)
+ nsISocketTransport createRoutedTransport([array, size_is(aTypeCount)]
+ in string aSocketTypes,
+ in unsigned long aTypeCount,
+ in AUTF8String aHost, // origin
+ in long aPort, // origin
+ in AUTF8String aHostRoute,
+ in long aPortRoute,
+ in nsIProxyInfo aProxyInfo);
+};
diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl
new file mode 100644
index 000000000..067edd3f0
--- /dev/null
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)]
+interface nsISpeculativeConnect : nsISupports
+{
+ /**
+ * Called as a hint to indicate a new transaction for the URI is likely coming
+ * soon. The implementer may use this information to start a TCP
+ * and/or SSL level handshake for that resource immediately so that it is
+ * ready and/or progressed when the transaction is actually submitted.
+ *
+ * No obligation is taken on by the implementer, nor is the submitter obligated
+ * to actually open the new channel.
+ *
+ * @param aURI the URI of the hinted transaction
+ * @param aPrincipal the principal that will be used for opening the
+ * channel of the hinted transaction.
+ * @param aCallbacks any security callbacks for use with SSL for interfaces
+ * such as nsIBadCertListener. May be null.
+ *
+ */
+ void speculativeConnect(in nsIURI aURI,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeConnect2(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeAnonymousConnect(in nsIURI aURI,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeAnonymousConnect2(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+};
+
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[builtinclass, uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+ /**
+ * Used to determine the maximum number of unused speculative connections
+ * we will have open for a host at any one time
+ */
+ [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+ /**
+ * Used to determine if we will ignore the existence of any currently idle
+ * connections when we decide whether or not to make a speculative
+ * connection.
+ */
+ [infallible] readonly attribute boolean ignoreIdle;
+
+ /*
+ * Used by the Predictor to gather telemetry data on speculative connection
+ * usage.
+ */
+ [infallible] readonly attribute boolean isFromPredictor;
+
+ /**
+ * by default speculative connections are not made to RFC 1918 addresses
+ */
+ [infallible] readonly attribute boolean allow1918;
+};
diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl
new file mode 100644
index 000000000..020555991
--- /dev/null
+++ b/netwerk/base/nsIStandardURL.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMutable.idl"
+
+interface nsIURI;
+
+/**
+ * nsIStandardURL defines the interface to an URL with the standard
+ * file path format common to protocols like http, ftp, and file.
+ * It supports initialization from a relative path and provides
+ * some customization on how URLs are normalized.
+ */
+[scriptable, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)]
+interface nsIStandardURL : nsIMutable
+{
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_STANDARD = 1;
+
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah://foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah://foo/bar
+ */
+ const unsigned long URLTYPE_AUTHORITY = 2;
+
+ /**
+ * blah:foo/bar => blah:///foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_NO_AUTHORITY = 3;
+
+ /**
+ * Initialize a standard URL.
+ *
+ * @param aUrlType - one of the URLTYPE_ flags listed above.
+ * @param aDefaultPort - if the port parsed from the URL string matches
+ * this port, then the port will be removed from the
+ * canonical form of the URL.
+ * @param aSpec - URL string.
+ * @param aOriginCharset - the charset from which this URI string
+ * originated. this corresponds to the charset
+ * that should be used when communicating this
+ * URI to an origin server, for example. if
+ * null, then provide aBaseURI implements this
+ * interface, the origin charset of aBaseURI will
+ * be assumed, otherwise defaulting to UTF-8 (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec will be resolved relative
+ * to aBaseURI.
+ */
+ void init(in unsigned long aUrlType,
+ in long aDefaultPort,
+ in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+ /**
+ * Set the default port.
+ *
+ * Note: If this object is already using its default port (i.e. if it has
+ * mPort == -1), then it will now implicitly be using the new default port.
+ *
+ * @param aNewDefaultPort - if the URI has (or is later given) a port that
+ * matches this default, then we won't include a
+ * port number in the canonical form of the URL.
+ */
+ void setDefaultPort(in long aNewDefaultPort);
+};
diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl
new file mode 100644
index 000000000..09ee408a1
--- /dev/null
+++ b/netwerk/base/nsIStreamListener.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIStreamListener
+ */
+[scriptable, uuid(3b4c8a77-76ba-4610-b316-678c73a3b88c)]
+interface nsIStreamListener : nsIRequestObserver
+{
+ /**
+ * Called when the next chunk of data (corresponding to the request) may
+ * be read without blocking the calling thread. The onDataAvailable impl
+ * must read exactly |aCount| bytes of data before returning.
+ *
+ * @param aRequest request corresponding to the source of the data
+ * @param aContext user defined context
+ * @param aInputStream input stream containing the data chunk
+ * @param aOffset
+ * Number of bytes that were sent in previous onDataAvailable calls
+ * for this request. In other words, the sum of all previous count
+ * parameters.
+ * @param aCount number of bytes available in the stream
+ *
+ * NOTE: The aInputStream parameter must implement readSegments.
+ *
+ * An exception thrown from onDataAvailable has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onDataAvailable(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsIInputStream aInputStream,
+ in unsigned long long aOffset,
+ in unsigned long aCount);
+};
diff --git a/netwerk/base/nsIStreamListenerTee.idl b/netwerk/base/nsIStreamListenerTee.idl
new file mode 100644
index 000000000..b7c3ae676
--- /dev/null
+++ b/netwerk/base/nsIStreamListenerTee.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+/**
+ * As data "flows" into a stream listener tee, it is copied to the output stream
+ * and then forwarded to the real listener.
+ */
+[scriptable, uuid(62b27fc1-6e8c-4225-8ad0-b9d44252973a)]
+interface nsIStreamListenerTee : nsIStreamListener
+{
+ /**
+ * Initalize the tee.
+ *
+ * @param listener
+ * the original listener the tee will propagate onStartRequest,
+ * onDataAvailable and onStopRequest notifications to, exceptions from
+ * the listener will be propagated back to the channel
+ * @param sink
+ * the stream the data coming from the channel will be written to,
+ * should be blocking
+ * @param requestObserver
+ * optional parameter, listener that gets only onStartRequest and
+ * onStopRequest notifications; exceptions threw within this optional
+ * observer are also propagated to the channel, but exceptions from
+ * the original listener (listener parameter) are privileged
+ */
+ void init(in nsIStreamListener listener,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+ /**
+ * Initalize the tee like above, but with the extra parameter to make it
+ * possible to copy the output asynchronously
+ * @param anEventTarget
+ * if set, this event-target is used to copy data to the output stream,
+ * giving an asynchronous tee
+ */
+ void initAsync(in nsIStreamListener listener,
+ in nsIEventTarget eventTarget,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+};
diff --git a/netwerk/base/nsIStreamLoader.idl b/netwerk/base/nsIStreamLoader.idl
new file mode 100644
index 000000000..274a07e9d
--- /dev/null
+++ b/netwerk/base/nsIStreamLoader.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIRequest;
+interface nsIStreamLoader;
+
+[scriptable, uuid(359F7990-D4E9-11d3-A1A5-0050041CAF44)]
+interface nsIStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(323bcff1-7513-4e1f-a541-1c9213c2ed1b)]
+interface nsIStreamLoader : nsIStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aStreamObserver
+ * An observer that will be notified when the data is complete.
+ * @param aRequestObserver
+ * An optional observer that will be notified when the request
+ * has started or stopped.
+ */
+ void init(in nsIStreamLoaderObserver aStreamObserver,
+ [optional] in nsIRequestObserver aRequestObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIStreamTransportService.idl b/netwerk/base/nsIStreamTransportService.idl
new file mode 100644
index 000000000..cd39f9cba
--- /dev/null
+++ b/netwerk/base/nsIStreamTransportService.idl
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransport;
+interface nsIInputStream;
+interface nsIOutputStream;
+
+/**
+ * This service read/writes a stream on a background thread.
+ *
+ * Use this service to transform any blocking stream (e.g., file stream)
+ * into a fully asynchronous stream that can be read/written without
+ * blocking the main thread.
+ */
+[scriptable, uuid(5e0adf7d-9785-45c3-a193-04f25a75da8f)]
+interface nsIStreamTransportService : nsISupports
+{
+ /**
+ * CreateInputTransport
+ *
+ * @param aStream
+ * The input stream that will be read on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aStartOffset
+ * The input stream will be read starting from this offset. Pass
+ * -1 to read from the current stream offset. NOTE: this parameter
+ * is ignored if the stream does not support nsISeekableStream.
+ * @param aReadLimit
+ * This parameter limits the number of bytes that will be read from
+ * the input stream. Pass -1 to read everything.
+ * @param aCloseWhenDone
+ * Specify this flag to have the input stream closed once its
+ * contents have been completely read.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createInputTransport(in nsIInputStream aStream,
+ in long long aStartOffset,
+ in long long aReadLimit,
+ in boolean aCloseWhenDone);
+
+ /**
+ * CreateOutputTransport
+ *
+ * @param aStream
+ * The output stream that will be written to on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aStartOffset
+ * The output stream will be written starting at this offset. Pass
+ * -1 to write to the current stream offset. NOTE: this parameter
+ * is ignored if the stream does not support nsISeekableStream.
+ * @param aWriteLimit
+ * This parameter limits the number of bytes that will be written to
+ * the output stream. Pass -1 for unlimited writing.
+ * @param aCloseWhenDone
+ * Specify this flag to have the output stream closed once its
+ * contents have been completely written.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createOutputTransport(in nsIOutputStream aStream,
+ in long long aStartOffset,
+ in long long aWriteLimit,
+ in boolean aCloseWhenDone);
+};
diff --git a/netwerk/base/nsIStreamingProtocolController.idl b/netwerk/base/nsIStreamingProtocolController.idl
new file mode 100644
index 000000000..249e8e983
--- /dev/null
+++ b/netwerk/base/nsIStreamingProtocolController.idl
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+interface nsIURI;
+
+#include "nsISupports.idl"
+
+%{C++
+#define MEDIASTREAM_FRAMETYPE_NORMAL 0x00000001
+#define MEDIASTREAM_FRAMETYPE_DISCONTINUITY 0x00000002
+#define MEDIASTREAM_FRAMETYPE_END_OF_STREAM 0x00000004
+%}
+
+/**
+ * Metadata of the media stream.
+ */
+[uuid(294adb30-856c-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolMetaData : nsISupports
+{
+ /**
+ * Frame type.
+ */
+ attribute uint32_t frameType;
+
+ /**
+ * The total tracks for the given media stream session.
+ */
+ attribute uint32_t totalTracks;
+
+ /**
+ * The mime type of the track.
+ */
+ attribute ACString mimeType;
+
+ /**
+ * The width of the resolution.
+ */
+ attribute unsigned long width;
+
+ /**
+ * The height of the resolution.
+ */
+ attribute unsigned long height;
+
+ /**
+ * The duration of the media stream in units of microseconds.
+ */
+ attribute unsigned long long duration;
+
+ /**
+ * The sample rate of the media stream.
+ */
+ attribute unsigned long sampleRate;
+
+ /**
+ * The timestamp indicates the stream absolute position
+ * relative to the beginning of the presentation.
+ */
+ attribute unsigned long long timeStamp;
+
+ /**
+ * The total number of audio channels in the media stream.
+ */
+ attribute unsigned long channelCount;
+
+ /**
+ * The AAC audio codec specific data.
+ */
+ attribute ACString esdsData;
+
+ /**
+ * The AVCC format extradata of H.264 stream.
+ */
+ attribute ACString avccData;
+};
+
+/**
+ * nsIStreamingProtocolListener
+ */
+[scriptable, uuid(c4f6b660-892e-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolListener : nsISupports
+{
+ /**
+ * Called when the data may be read without blocking the calling thread.
+ * @param index The track number of the media stream.
+ * @param data Raw data of the media stream on given track number.
+ * @param length The length of the raw data.
+ * @param offset The offset in the data stream from the start of the media
+ * presentation in bytes.
+ * @param meta The meta data of the frame.
+ */
+ void onMediaDataAvailable(in uint8_t index,
+ in ACString data,
+ in uint32_t length,
+ in uint32_t offset,
+ in nsIStreamingProtocolMetaData meta);
+
+ /**
+ * Called when the meta data for a given session is available.
+ * @param index The track number of the media stream.
+ * @param meta The meta data of the media stream.
+ */
+ void onConnected(in uint8_t index, in nsIStreamingProtocolMetaData meta);
+
+ /**
+ * Called when the Rtsp session is closed.
+ * @param index Track number of the media stream.
+ * @param reason The reason of disconnection.
+ */
+ void onDisconnected(in uint8_t index, in nsresult reason);
+};
+
+/**
+ * Media stream controller API: control and retrieve meta data from media stream.
+ */
+[uuid(4ce040f0-c50d-461f-94e2-af5a77fe13a5)]
+interface nsIStreamingProtocolController : nsISupports
+{
+ /**
+ * Preprare the URI before we can start the connection.
+ * @param aUri The URI of the media stream.
+ */
+ void init(in nsIURI aUri);
+
+ /**
+ * Asynchronously open this controller. Data is fed to the specified
+ * media stream listener as it becomes available. If asyncOpen returns
+ * successfully, the controller is responsible for keeping itself alive
+ * until it has called onStopRequest on aListener.
+ *
+ * @param aListener The nsIStreamingProtocolListener implementation
+ */
+ void asyncOpen(in nsIStreamingProtocolListener aListener);
+
+ /*
+ * Get the metadata of a track.
+ * @param index Index of a track.
+ * @return A nsIStreamingProtocolMetaData.
+ */
+ nsIStreamingProtocolMetaData getTrackMetaData(in octet index);
+
+ /*
+ * Tell the streaming server to start sending media data.
+ */
+ void play();
+
+ /*
+ * Tell the streaming server to pause sending media data.
+ */
+ void pause();
+
+ /*
+ * Tell the streaming server to resume the suspended media stream.
+ */
+ void resume();
+
+ /*
+ * Tell the streaming server to suspend the media stream.
+ */
+ void suspend();
+
+ /*
+ * Tell the streaming server to send media data in specific time.
+ * @param seekTimeUs Start time of the media stream in microseconds.
+ */
+ void seek(in unsigned long long seekTimeUs);
+
+ /*
+ * Tell the streaming server to stop the
+ * media stream and close the connection.
+ */
+ void stop();
+
+ /*
+ * Notify the streaming controller that the playback has ended.
+ * The controller might have to perform certain internal state transition.
+ */
+ void playbackEnded();
+
+ /**
+ * Total number of audio/video tracks.
+ */
+ readonly attribute octet totalTracks;
+};
diff --git a/netwerk/base/nsIStreamingProtocolService.idl b/netwerk/base/nsIStreamingProtocolService.idl
new file mode 100644
index 000000000..a0fae164e
--- /dev/null
+++ b/netwerk/base/nsIStreamingProtocolService.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface nsIStreamingProtocolController;
+interface nsIChannel;
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_MEDIASTREAMCONTROLLERSERVICE_CID \
+ { 0x94838530, 0x8627, 0x11e2, \
+ { \
+ 0x9e, 0x96, 0x08, 0x00, \
+ 0x20, 0x0c, 0x9a, 0x66 \
+ } \
+ }
+#define MEDIASTREAMCONTROLLERSERVICE_CONTRACTID \
+ "@mozilla.org/mediastream/mediastreamcontrollerservice;1"
+%}
+
+/**
+ * Media stream controller Service API.
+ */
+[uuid(94838530-8627-11e2-9e96-0800200c9a66)]
+interface nsIStreamingProtocolControllerService : nsISupports
+{
+ /*
+ * Create a new media stream controller from the given channel.
+ * @param channel nsIChannel for the given URI.
+ */
+ nsIStreamingProtocolController create(in nsIChannel channel);
+};
diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl
new file mode 100644
index 000000000..9a46dda78
--- /dev/null
+++ b/netwerk/base/nsISyncStreamListener.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+[scriptable, uuid(7e1aa658-6e3f-4521-9946-9685a169f764)]
+interface nsISyncStreamListener : nsIStreamListener
+{
+ /**
+ * Returns an input stream that when read will fetch data delivered to the
+ * sync stream listener. The nsIInputStream implementation will wait for
+ * OnDataAvailable events before returning from Read.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+ readonly attribute nsIInputStream inputStream;
+};
diff --git a/netwerk/base/nsISystemProxySettings.idl b/netwerk/base/nsISystemProxySettings.idl
new file mode 100644
index 000000000..55be7f313
--- /dev/null
+++ b/netwerk/base/nsISystemProxySettings.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to use platform-specific proxy
+ * settings when the proxy preference is set to "automatic discovery". This service
+ * acts like a PAC parser to netwerk, but it will actually read the system settings and
+ * either return the proper proxy data from the autoconfig URL specified in the system proxy,
+ * or generate proxy data based on the system's manual proxy settings.
+ */
+[scriptable, uuid(971591cd-277e-409a-bbf6-0a79879cd307)]
+interface nsISystemProxySettings : nsISupports
+{
+ /**
+ * Whether or not it is appropriate to execute getProxyForURI off the main thread.
+ * If that method can block (e.g. for WPAD as windows does) then it must be
+ * not mainThreadOnly to avoid creating main thread jank. The main thread only option is
+ * provided for implementations that do not block but use other main thread only
+ * functions such as dbus.
+ */
+ readonly attribute bool mainThreadOnly;
+
+ /**
+ * If non-empty, use this PAC file. If empty, call getProxyForURI instead.
+ */
+ readonly attribute AUTF8String PACURI;
+
+ /**
+ * See ProxyAutoConfig::getProxyForURI; this function behaves similarly except
+ * a more relaxed return string is allowed that includes full urls instead of just
+ * host:port syntax. e.g. "PROXY http://www.foo.com:8080" instead of
+ * "PROXY www.foo.com:8080"
+ */
+ AUTF8String getProxyForURI(in AUTF8String testSpec,
+ in AUTF8String testScheme,
+ in AUTF8String testHost,
+ in int32_t testPort);
+};
diff --git a/netwerk/base/nsITLSServerSocket.idl b/netwerk/base/nsITLSServerSocket.idl
new file mode 100644
index 000000000..9a03c2ead
--- /dev/null
+++ b/netwerk/base/nsITLSServerSocket.idl
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServerSocket.idl"
+
+interface nsIX509Cert;
+interface nsITLSServerSecurityObserver;
+interface nsISocketTransport;
+
+[scriptable, uuid(cc2c30f9-cfaa-4b8a-bd44-c24881981b74)]
+interface nsITLSServerSocket : nsIServerSocket
+{
+ /**
+ * serverCert
+ *
+ * The server's certificate that is presented to the client during the TLS
+ * handshake. This is required to be set before calling |asyncListen|.
+ */
+ attribute nsIX509Cert serverCert;
+
+ /**
+ * setSessionCache
+ *
+ * Whether the server should use a session cache. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionCache(in boolean aSessionCache);
+
+ /**
+ * setSessionTickets
+ *
+ * Whether the server should support session tickets. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionTickets(in boolean aSessionTickets);
+
+ /**
+ * Values for setRequestClientCertificate
+ */
+ // Never request
+ const unsigned long REQUEST_NEVER = 0;
+ // Request (but do not require) during the first handshake only
+ const unsigned long REQUEST_FIRST_HANDSHAKE = 1;
+ // Request (but do not require) during each handshake
+ const unsigned long REQUEST_ALWAYS = 2;
+ // Require during the first handshake only
+ const unsigned long REQUIRE_FIRST_HANDSHAKE = 3;
+ // Require during each handshake
+ const unsigned long REQUIRE_ALWAYS = 4;
+
+ /**
+ * setRequestClientCertificate
+ *
+ * Whether the server should request and/or require a client auth certificate
+ * from the client. Defaults to REQUEST_NEVER. See the possible options
+ * above. This should be set before calling |asyncListen| if you wish to
+ * change the default.
+ */
+ void setRequestClientCertificate(in unsigned long aRequestClientCert);
+
+ /**
+ * setCipherSuites
+ *
+ * The server's cipher suites that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ */
+ void setCipherSuites([array, size_is(aLength)] in unsigned short aCipherSuites,
+ in unsigned long aLength);
+
+ /**
+ * setVersionRange
+ *
+ * The server's TLS versions that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ *
+ * aMinVersion and aMaxVersion is a TLS version value from
+ * |nsITLSClientStatus| constants.
+ */
+ void setVersionRange(in unsigned short aMinVersion,
+ in unsigned short aMaxVersion);
+};
+
+/**
+ * Security summary for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.
+ *
+ * This is accessible through the security info object on the transport, which
+ * will be an instance of |nsITLSServerConnectionInfo| (see below).
+ *
+ * The values of these attributes are available once the |onHandshakeDone|
+ * method of the security observer has been called (see
+ * |nsITLSServerSecurityObserver| below).
+ */
+[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)]
+interface nsITLSClientStatus : nsISupports
+{
+ /**
+ * peerCert
+ *
+ * The client's certificate, if one was requested via |requestCertificate|
+ * above and supplied by the client.
+ */
+ readonly attribute nsIX509Cert peerCert;
+
+ /**
+ * Values for tlsVersionUsed, as defined by TLS
+ */
+ const short SSL_VERSION_3 = 0x0300;
+ const short TLS_VERSION_1 = 0x0301;
+ const short TLS_VERSION_1_1 = 0x0302;
+ const short TLS_VERSION_1_2 = 0x0303;
+ const short TLS_VERSION_1_3 = 0x0304;
+ const short TLS_VERSION_UNKNOWN = -1;
+
+ /**
+ * tlsVersionUsed
+ *
+ * The version of TLS used by the connection. See values above.
+ */
+ readonly attribute short tlsVersionUsed;
+
+ /**
+ * cipherName
+ *
+ * Name of the cipher suite used, such as
+ * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".
+ * See security/nss/lib/ssl/sslinfo.c for the possible values.
+ */
+ readonly attribute ACString cipherName;
+
+ /**
+ * keyLength
+ *
+ * The "effective" key size of the symmetric key in bits.
+ */
+ readonly attribute unsigned long keyLength;
+
+ /**
+ * macLength
+ *
+ * The size of the MAC in bits.
+ */
+ readonly attribute unsigned long macLength;
+};
+
+/**
+ * Connection info for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server. This object is thread-safe.
+ *
+ * This is exposed as the security info object on the transport, so it can be
+ * accessed via |transport.securityInfo|.
+ *
+ * This interface is available by the time the |onSocketAttached| is called,
+ * which is the first time the TLS server consumer is notified of a new client.
+ */
+[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)]
+interface nsITLSServerConnectionInfo : nsISupports
+{
+ /**
+ * setSecurityObserver
+ *
+ * Set the security observer to be notified when the TLS handshake has
+ * completed.
+ */
+ void setSecurityObserver(in nsITLSServerSecurityObserver observer);
+
+ /**
+ * serverSocket
+ *
+ * The nsITLSServerSocket instance that accepted this client connection.
+ */
+ readonly attribute nsITLSServerSocket serverSocket;
+
+ /**
+ * status
+ *
+ * Security summary for this TLS client connection. Note that the values of
+ * this interface are not available until the TLS handshake has completed.
+ * See |nsITLSClientStatus| above for more details.
+ */
+ readonly attribute nsITLSClientStatus status;
+};
+
+[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)]
+interface nsITLSServerSecurityObserver : nsISupports
+{
+ /**
+ * onHandsakeDone
+ *
+ * This method is called once the TLS handshake is completed. This takes
+ * place after |onSocketAccepted| has been called, which typically opens the
+ * streams to keep things moving along. It's important to be aware that the
+ * handshake has not completed at the point that |onSocketAccepted| is called,
+ * so no security verification can be done until this method is called.
+ */
+ void onHandshakeDone(in nsITLSServerSocket aServer,
+ in nsITLSClientStatus aStatus);
+};
diff --git a/netwerk/base/nsIThreadRetargetableRequest.idl b/netwerk/base/nsIThreadRetargetableRequest.idl
new file mode 100644
index 000000000..9a93b70c6
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableRequest.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIEventTarget;
+
+/**
+ * nsIThreadRetargetableRequest
+ *
+ * Should be implemented by requests that support retargeting delivery of
+ * data off the main thread.
+ */
+[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
+interface nsIThreadRetargetableRequest : nsISupports
+{
+ /**
+ * Called to retarget delivery of OnDataAvailable to another thread. Should
+ * only be called before AsyncOpen for nsIWebsocketChannels, or during
+ * OnStartRequest for nsIChannels.
+ * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be
+ * delivered on the main thread.
+ *
+ * @param aNewTarget New event target, e.g. thread or threadpool.
+ *
+ * Note: no return value is given. If the retargeting cannot be handled,
+ * normal delivery to the main thread will continue. As such, listeners
+ * should be ready to deal with OnDataAvailable on either the main thread or
+ * the new target thread.
+ */
+ void retargetDeliveryTo(in nsIEventTarget aNewTarget);
+};
diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl
new file mode 100644
index 000000000..428807a15
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIThreadRetargetableStreamListener
+ *
+ * To be used by classes which implement nsIStreamListener and whose
+ * OnDataAvailable callback may be retargeted for delivery off the main thread.
+ */
+[uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)]
+interface nsIThreadRetargetableStreamListener : nsISupports
+{
+ /**
+ * Checks this listener and any next listeners it may have to verify that
+ * they can receive OnDataAvailable off the main thread. It is the
+ * responsibility of the implementing class to decide on the criteria to
+ * determine if retargeted delivery of these methods is possible, but it must
+ * check any and all nsIStreamListener objects that might be called in the
+ * listener chain.
+ *
+ * An exception should be thrown if a listener in the chain does not
+ * support retargeted delivery, i.e. if the next listener does not implement
+ * nsIThreadRetargetableStreamListener, or a call to its checkListenerChain()
+ * fails.
+ */
+ void checkListenerChain();
+};
+
diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl
new file mode 100644
index 000000000..76b8cc2a5
--- /dev/null
+++ b/netwerk/base/nsIThrottledInputChannel.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIAsyncInputStream;
+
+/**
+ * An instance of this interface can be used to throttle the uploads
+ * of a group of associated channels.
+ */
+[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)]
+interface nsIInputChannelThrottleQueue : nsISupports
+{
+ /**
+ * Initialize this object with the mean and maximum bytes per
+ * second that will be allowed. Neither value may be zero, and
+ * the maximum must not be less than the mean.
+ *
+ * @param aMeanBytesPerSecond
+ * Mean number of bytes per second.
+ * @param aMaxBytesPerSecond
+ * Maximum number of bytes per second.
+ */
+ void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond);
+
+ /**
+ * Return the number of bytes that are available to the caller in
+ * this time slice.
+ *
+ * @param aRemaining
+ * The number of bytes available to be processed
+ * @return the number of bytes allowed to be processed during this
+ * time slice; this will never be greater than aRemaining.
+ */
+ unsigned long available(in unsigned long aRemaining);
+
+ /**
+ * Record a successful read.
+ *
+ * @param aBytesRead
+ * The number of bytes actually read.
+ */
+ void recordRead(in unsigned long aBytesRead);
+
+ /**
+ * Return the number of bytes allowed through this queue. This is
+ * the sum of all the values passed to recordRead. This method is
+ * primarily useful for testing.
+ */
+ unsigned long long bytesProcessed();
+
+ /**
+ * Wrap the given input stream in a new input stream which
+ * throttles the incoming data.
+ *
+ * @param aInputStream the input stream to wrap
+ * @return a new input stream that throttles the data.
+ */
+ nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream);
+};
+
+/**
+ * A throttled input channel can be managed by an
+ * nsIInputChannelThrottleQueue to limit how much data is sent during
+ * a given time slice.
+ */
+[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)]
+interface nsIThrottledInputChannel : nsISupports
+{
+ /**
+ * The queue that manages this channel. Multiple channels can
+ * share a single queue. A null value means that no throttling
+ * will be done.
+ */
+ attribute nsIInputChannelThrottleQueue throttleQueue;
+};
diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl
new file mode 100644
index 000000000..6ec2d1ff8
--- /dev/null
+++ b/netwerk/base/nsITimedChannel.idl
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIPrincipal;
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+// All properties return zero if the value is not available
+[scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)]
+interface nsITimedChannel : nsISupports {
+ // Set this attribute to true to enable collection of timing data.
+ // channelCreationTime will be available even with this attribute set to
+ // false.
+ attribute boolean timingEnabled;
+
+ // The number of redirects
+ attribute uint16_t redirectCount;
+
+ [noscript] readonly attribute TimeStamp channelCreation;
+ [noscript] readonly attribute TimeStamp asyncOpen;
+
+ // The following are only set when the document is not (only) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp domainLookupStart;
+ [noscript] readonly attribute TimeStamp domainLookupEnd;
+ [noscript] readonly attribute TimeStamp connectStart;
+ [noscript] readonly attribute TimeStamp connectEnd;
+ [noscript] readonly attribute TimeStamp requestStart;
+ [noscript] readonly attribute TimeStamp responseStart;
+ [noscript] readonly attribute TimeStamp responseEnd;
+
+ // The redirect attributes timings must be writeble, se we can transfer
+ // the data from one channel to the redirected channel.
+ [noscript] attribute TimeStamp redirectStart;
+ [noscript] attribute TimeStamp redirectEnd;
+
+ // The initiator type
+ [noscript] attribute AString initiatorType;
+
+ // This flag should be set to false only if a cross-domain redirect occurred
+ [noscript] attribute boolean allRedirectsSameOrigin;
+ // This flag is set to false if the timing allow check fails
+ [noscript] attribute boolean allRedirectsPassTimingAllowCheck;
+ // Implements the timing-allow-check to determine if we should report
+ // timing info for the resourceTiming object.
+ [noscript] boolean timingAllowCheck(in nsIPrincipal origin);
+ %{C++
+ inline bool TimingAllowCheck(nsIPrincipal* aOrigin) {
+ bool allowed = false;
+ return NS_SUCCEEDED(TimingAllowCheck(aOrigin, &allowed)) && allowed;
+ }
+ %}
+
+ // The following are only set if the document is (partially) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp cacheReadStart;
+ [noscript] readonly attribute TimeStamp cacheReadEnd;
+
+ // All following are PRTime versions of the above.
+ readonly attribute PRTime channelCreationTime;
+ readonly attribute PRTime asyncOpenTime;
+ readonly attribute PRTime domainLookupStartTime;
+ readonly attribute PRTime domainLookupEndTime;
+ readonly attribute PRTime connectStartTime;
+ readonly attribute PRTime connectEndTime;
+ readonly attribute PRTime requestStartTime;
+ readonly attribute PRTime responseStartTime;
+ readonly attribute PRTime responseEndTime;
+ readonly attribute PRTime cacheReadStartTime;
+ readonly attribute PRTime cacheReadEndTime;
+ readonly attribute PRTime redirectStartTime;
+ readonly attribute PRTime redirectEndTime;
+};
diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl
new file mode 100644
index 000000000..d639d3310
--- /dev/null
+++ b/netwerk/base/nsITraceableChannel.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows one to intercept its data by
+ * inserting intermediate stream listeners.
+ */
+[scriptable, uuid(68167b0b-ef34-4d79-a09a-8045f7c5140e)]
+interface nsITraceableChannel : nsISupports
+{
+ /*
+ * Replace the channel's listener with a new one, and return the listener
+ * the channel used to have. The new listener intercepts OnStartRequest,
+ * OnDataAvailable and OnStopRequest calls and must pass them to
+ * the original listener after examination. If multiple callers replace
+ * the channel's listener, a chain of listeners is created.
+ * The caller of setNewListener has no way to control at which place
+ * in the chain its listener is placed.
+ *
+ * Note: The caller of setNewListener must not delay passing
+ * OnStartRequest to the original listener.
+ *
+ * Note2: A channel may restrict when the listener can be replaced.
+ * It is not recommended to allow listener replacement after OnStartRequest
+ * has been called.
+ */
+ nsIStreamListener setNewListener(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl
new file mode 100644
index 000000000..2730c3a29
--- /dev/null
+++ b/netwerk/base/nsITransport.idl
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITransportEventSink;
+interface nsIEventTarget;
+
+/**
+ * nsITransport
+ *
+ * This interface provides a common way of accessing i/o streams connected
+ * to some resource. This interface does not in any way specify the resource.
+ * It provides methods to open blocking or non-blocking, buffered or unbuffered
+ * streams to the resource. The name "transport" is meant to connote the
+ * inherent data transfer implied by this interface (i.e., data is being
+ * transfered in some fashion via the streams exposed by this interface).
+ *
+ * A transport can have an event sink associated with it. The event sink
+ * receives transport-specific events as the transfer is occuring. For a
+ * socket transport, these events can include status about the connection.
+ * See nsISocketTransport for more info about socket transport specifics.
+ */
+[scriptable, uuid(2a8c6334-a5e6-4ec3-9865-1256541446fb)]
+interface nsITransport : nsISupports
+{
+ /**
+ * Open flags.
+ */
+ const unsigned long OPEN_BLOCKING = 1<<0;
+ const unsigned long OPEN_UNBUFFERED = 1<<1;
+
+ /**
+ * Open an input stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream has no data and is not
+ * closed, then reading from it will block the calling thread until
+ * at least one byte is available or until the stream is closed.
+ * If this flag is NOT specified, then the stream has non-blocking
+ * stream semantics. This means that if the stream has no data and is
+ * not closed, then reading from it returns NS_BASE_STREAM_WOULD_BLOCK.
+ * In addition, in non-blocking mode, the stream is guaranteed to
+ * support nsIAsyncInputStream. This interface allows the consumer of
+ * the stream to be notified when the stream can again be read.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support ReadSegments.
+ * ReadSegments is only gauranteed to be implemented when this flag is
+ * NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIInputStream openInputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Open an output stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream is full and is not closed,
+ * then writing to it will block the calling thread until ALL of the
+ * data can be written or until the stream is closed. If this flag is
+ * NOT specified, then the stream has non-blocking stream semantics.
+ * This means that if the stream is full and is not closed, then writing
+ * to it returns NS_BASE_STREAM_WOULD_BLOCK. In addition, in non-
+ * blocking mode, the stream is guaranteed to support
+ * nsIAsyncOutputStream. This interface allows the consumer of the
+ * stream to be notified when the stream can again accept more data.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support WriteSegments and
+ * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be
+ * implemented when this flag is NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIOutputStream openOutputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Close the transport and any open streams.
+ *
+ * @param aReason
+ * the reason for closing the stream.
+ */
+ void close(in nsresult aReason);
+
+ /**
+ * Set the transport event sink.
+ *
+ * @param aSink
+ * receives transport layer notifications
+ * @param aEventTarget
+ * indicates the event target to which the notifications should
+ * be delivered. if NULL, then the notifications may occur on
+ * any thread.
+ */
+ void setEventSink(in nsITransportEventSink aSink,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * Generic nsITransportEventSink status codes. nsITransport
+ * implementations may override these status codes with their own more
+ * specific status codes (e.g., see nsISocketTransport).
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_READING = 0x804b0008;
+ const unsigned long STATUS_WRITING = 0x804b0009;
+};
+
+[scriptable, uuid(EDA4F520-67F7-484b-A691-8C3226A5B0A6)]
+interface nsITransportEventSink : nsISupports
+{
+ /**
+ * Transport status notification.
+ *
+ * @param aTransport
+ * the transport sending this status notification.
+ * @param aStatus
+ * the transport status (resolvable to a string using
+ * nsIErrorService). See nsISocketTransport for socket specific
+ * status codes and more comments.
+ * @param aProgress
+ * the amount of data either read or written depending on the value
+ * of the status code. this value is relative to aProgressMax.
+ * @param aProgressMax
+ * the maximum amount of data that will be read or written. if
+ * unknown, -1 will be passed.
+ */
+ void onTransportStatus(in nsITransport aTransport,
+ in nsresult aStatus,
+ in long long aProgress,
+ in long long aProgressMax);
+};
diff --git a/netwerk/base/nsIUDPSocket.idl b/netwerk/base/nsIUDPSocket.idl
new file mode 100644
index 000000000..9a92bba2b
--- /dev/null
+++ b/netwerk/base/nsIUDPSocket.idl
@@ -0,0 +1,353 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetAddr;
+interface nsIUDPSocketListener;
+interface nsIUDPMessage;
+interface nsISocketTransport;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>);
+
+/**
+ * nsIUDPSocket
+ *
+ * An interface to a UDP socket that can accept incoming connections.
+ */
+[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)]
+interface nsIUDPSocket : nsISupports
+{
+ /**
+ * init
+ *
+ * This method initializes a UDP socket.
+ *
+ * @param aPort
+ * The port of the UDP socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the UDP socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [optional_argc] void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ [optional_argc] void init2(in AUTF8String aAddr,
+ in long aPort,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a UDP socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this UDP socket should be bound.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * close
+ *
+ * This method closes a UDP socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this UDP socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the UDP socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIUDPSocketListener aListener);
+
+ /**
+ * connect
+ *
+ * This method connects the UDP socket to a remote UDP address.
+ *
+ * @param aRemoteAddr
+ * The remote address to connect to
+ */
+ void connect([const] in NetAddrPtr aAddr);
+
+ /**
+ * Returns the local address of this UDP socket
+ */
+ readonly attribute nsINetAddr localAddr;
+
+ /**
+ * Returns the port of this UDP socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this UDP socket is bound. Since a
+ * UDP socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a UDP socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] NetAddr getAddress();
+
+ /**
+ * send
+ *
+ * Send out the datagram to specified remote host and port.
+ * DNS lookup will be triggered.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ unsigned long send(in AUTF8String host, in unsigned short port,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendWithAddr
+ *
+ * Send out the datagram to specified remote host and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ unsigned long sendWithAddr(in nsINetAddr addr,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @param dataLength The maximum number of bytes to be written.
+ * @return number of bytes written. (0 or dataLength)
+ */
+ [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr,
+ [const, array, size_is(dataLength)]in uint8_t data,
+ in unsigned long dataLength);
+
+ /**
+ * sendBinaryStream
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStream(in AUTF8String host, in unsigned short port,
+ in nsIInputStream stream);
+
+ /**
+ * sendBinaryStreamWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStreamWithAddress([const] in NetAddrPtr addr,
+ in nsIInputStream stream);
+
+ /**
+ * joinMulticast
+ *
+ * Join the multicast group specified by |addr|. You are then able to
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to join the group. If
+ * this is not specified, the OS may join the group on all interfaces
+ * or only the primary interface.
+ */
+ void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void joinMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * leaveMulticast
+ *
+ * Leave the multicast group specified by |addr|. You will no longer
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to leave the group.
+ * If this is not specified, the OS may leave the group on all
+ * interfaces or only the primary interface.
+ */
+ void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void leaveMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * multicastLoopback
+ *
+ * Whether multicast datagrams sent via this socket should be looped back to
+ * this host (assuming this host has joined the relevant group). Defaults
+ * to true.
+ * Note: This is currently write-only.
+ */
+ attribute boolean multicastLoopback;
+
+ /**
+ * multicastInterface
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ attribute AUTF8String multicastInterface;
+
+ /**
+ * multicastInterfaceAddr
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ [noscript] attribute NetAddr multicastInterfaceAddr;
+
+ /**
+ * recvBufferSize
+ *
+ * The size of the receive buffer. Default depends on the OS.
+ */
+ [noscript] attribute long recvBufferSize;
+
+ /**
+ * sendBufferSize
+ *
+ * The size of the send buffer. Default depends on the OS.
+ */
+ [noscript] attribute long sendBufferSize;
+};
+
+/**
+ * nsIUDPSocketListener
+ *
+ * This interface is notified whenever a UDP socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)]
+interface nsIUDPSocketListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket,
+ in nsIUDPMessage aMessage);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
+
+/**
+ * nsIUDPMessage
+ *
+ * This interface is used to encapsulate an incomming UDP message
+ */
+[scriptable, uuid(afdc743f-9cc0-40d8-b442-695dc54bbb74)]
+interface nsIUDPMessage : nsISupports
+{
+ /**
+ * Address of the source of the message
+ */
+ readonly attribute nsINetAddr fromAddr;
+
+ /**
+ * Data of the message
+ */
+ readonly attribute ACString data;
+
+ /**
+ * Stream to send a response
+ */
+ readonly attribute nsIOutputStream outputStream;
+
+ /**
+ * Raw Data of the message
+ */
+ [implicit_jscontext] readonly attribute jsval rawData;
+ [noscript, notxpcom, nostdcall] Uint8TArrayRef getDataAsTArray();
+};
diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl
new file mode 100644
index 000000000..2384c5fd9
--- /dev/null
+++ b/netwerk/base/nsIURI.idl
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * URIs are essentially structured names for things -- anything. This interface
+ * provides accessors to set and query the most basic components of an URI.
+ * Subclasses, including nsIURL, impose greater structure on the URI.
+ *
+ * This interface follows Tim Berners-Lee's URI spec (RFC2396) [1], where the
+ * basic URI components are defined as such:
+ * <pre>
+ * ftp://username:password@hostname:portnumber/pathname#ref
+ * \ / \ / \ / \ /\ \ /
+ * - --------------- ------ -------- | -
+ * | | | | | |
+ * | | | | | Ref
+ * | | | Port \ /
+ * | | Host / --------
+ * | UserPass / |
+ * Scheme / Path
+ * \ /
+ * --------------------------------
+ * |
+ * PrePath
+ * </pre>
+ * The definition of the URI components has been extended to allow for
+ * internationalized domain names [2] and the more generic IRI structure [3].
+ *
+ * Note also that the RFC defines #-separated fragment identifiers as being
+ * "not part of the URI". Despite this, we bundle them as part of the URI, for
+ * convenience.
+ *
+ * [1] http://www.ietf.org/rfc/rfc2396.txt
+ * [2] http://www.ietf.org/internet-drafts/draft-ietf-idn-idna-06.txt
+ * [3] http://www.ietf.org/internet-drafts/draft-masinter-url-i18n-08.txt
+ */
+
+%{C++
+#include "nsStringGlue.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+%}
+
+/**
+ * nsIURI - interface for an uniform resource identifier w/ i18n support.
+ *
+ * AUTF8String attributes may contain unescaped UTF-8 characters.
+ * Consumers should be careful to escape the UTF-8 strings as necessary, but
+ * should always try to "display" the UTF-8 version as provided by this
+ * interface.
+ *
+ * AUTF8String attributes may also contain escaped characters.
+ *
+ * Unescaping URI segments is unadvised unless there is intimate
+ * knowledge of the underlying charset or there is no plan to display (or
+ * otherwise enforce a charset on) the resulting URI substring.
+ *
+ * The correct way to create an nsIURI from a string is via
+ * nsIIOService.newURI.
+ *
+ * NOTE: nsBinaryInputStream::ReadObject contains a hackaround to intercept the
+ * old (pre-gecko6) nsIURI IID and swap in the current IID instead, in order
+ * for sessionstore to work after an upgrade. If this IID is revved further,
+ * we will need to add additional checks there for all intermediate IIDs, until
+ * nsPrincipal is fixed to serialize its URIs as nsISupports (bug 662693).
+ */
+[scriptable, uuid(92073a54-6d78-4f30-913a-b871813208c6)]
+interface nsIURI : nsISupports
+{
+ /************************************************************************
+ * The URI is broken down into the following principal components:
+ */
+
+ /**
+ * Returns a string representation of the URI. Setting the spec causes
+ * the new spec to be parsed per the rules for the scheme the URI
+ * currently has. In particular, setting the spec to a URI string with a
+ * different scheme will generally produce incorrect results; no one
+ * outside of a protocol handler implementation should be doing that. If
+ * the URI stores information from the nsIIOService.newURI call used to
+ * create it other than just the parsed string, then behavior of this
+ * information on setting the spec attribute is undefined.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String spec;
+
+%{ C++
+ // An infallible wrapper for GetSpec() that returns a failure indication
+ // string if GetSpec() fails. It is most useful for creating
+ // logging/warning/error messages produced for human consumption, and when
+ // matching a URI spec against a fixed spec such as about:blank.
+ nsCString GetSpecOrDefault()
+ {
+ nsCString spec;
+ nsresult rv = GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ spec.Assign("[nsIURI::GetSpec failed]");
+ }
+ return spec;
+ }
+%}
+
+ /**
+ * The prePath (eg. scheme://user:password@host:port) returns the string
+ * before the path. This is useful for authentication or managing sessions.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String prePath;
+
+ /**
+ * The Scheme is the protocol to which this URI refers. The scheme is
+ * restricted to the US-ASCII charset per RFC2396. Setting this is
+ * highly discouraged outside of a protocol handler implementation, since
+ * that will generally lead to incorrect results.
+ */
+ attribute ACString scheme;
+
+ /**
+ * The username:password (or username only if value doesn't contain a ':')
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String userPass;
+
+ /**
+ * The optional username and password, assuming the preHost consists of
+ * username:password.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String username;
+ attribute AUTF8String password;
+
+ /**
+ * The host:port (or simply the host, if port == -1).
+ *
+ * If this attribute is set to a value that only has a host part, the port
+ * will not be reset. To reset the port as well use setHostAndPort.
+ *
+ * Characters are NOT escaped.
+ */
+ attribute AUTF8String hostPort;
+
+ /**
+ * This function will always set a host and a port. If the port part is
+ * empty, the value of the port will be set to the default value.
+ */
+ void setHostAndPort(in AUTF8String hostport);
+
+ /**
+ * The host is the internet domain name to which this URI refers. It could
+ * be an IPv4 (or IPv6) address literal. If supported, it could be a
+ * non-ASCII internationalized domain name.
+ *
+ * Characters are NOT escaped.
+ */
+ attribute AUTF8String host;
+
+ /**
+ * A port value of -1 corresponds to the protocol's default port (eg. -1
+ * implies port 80 for http URIs).
+ */
+ attribute long port;
+
+ /**
+ * The path, typically including at least a leading '/' (but may also be
+ * empty, depending on the protocol).
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String path;
+
+
+ /************************************************************************
+ * An URI supports the following methods:
+ */
+
+ /**
+ * URI equivalence test (not a strict string comparison).
+ *
+ * eg. http://foo.com:80/ == http://foo.com/
+ */
+ boolean equals(in nsIURI other);
+
+ /**
+ * An optimization to do scheme checks without requiring the users of nsIURI
+ * to GetScheme, thereby saving extra allocating and freeing. Returns true if
+ * the schemes match (case ignored).
+ */
+ boolean schemeIs(in string scheme);
+
+ /**
+ * Clones the current URI.
+ */
+ nsIURI clone();
+
+ /**
+ * This method resolves a relative string into an absolute URI string,
+ * using this URI as the base.
+ *
+ * NOTE: some implementations may have no concept of a relative URI.
+ */
+ AUTF8String resolve(in AUTF8String relativePath);
+
+
+ /************************************************************************
+ * Additional attributes:
+ */
+
+ /**
+ * The URI spec with an ASCII compatible encoding. Host portion follows
+ * the IDNA draft spec. Other parts are URL-escaped per the rules of
+ * RFC2396. The result is strictly ASCII.
+ */
+ readonly attribute ACString asciiSpec;
+
+ /**
+ * The host:port (or simply the host, if port == -1), with an ASCII compatible
+ * encoding. Host portion follows the IDNA draft spec. The result is strictly
+ * ASCII.
+ */
+ readonly attribute ACString asciiHostPort;
+
+ /**
+ * The URI host with an ASCII compatible encoding. Follows the IDNA
+ * draft spec for converting internationalized domain names (UTF-8) to
+ * ASCII for compatibility with existing internet infrasture.
+ */
+ readonly attribute ACString asciiHost;
+
+ /**
+ * The charset of the document from which this URI originated. An empty
+ * value implies UTF-8.
+ *
+ * If this value is something other than UTF-8 then the URI components
+ * (e.g., spec, prePath, username, etc.) will all be fully URL-escaped.
+ * Otherwise, the URI components may contain unescaped multibyte UTF-8
+ * characters.
+ */
+ readonly attribute ACString originCharset;
+
+ /************************************************************************
+ * Additional attribute & methods added for .ref support:
+ */
+
+ /**
+ * Returns the reference portion (the part after the "#") of the URI.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String ref;
+
+ /**
+ * URI equivalence test (not a strict string comparison), ignoring
+ * the value of the .ref member.
+ *
+ * eg. http://foo.com/# == http://foo.com/
+ * http://foo.com/#aaa == http://foo.com/#bbb
+ */
+ boolean equalsExceptRef(in nsIURI other);
+
+ /**
+ * Clones the current URI, clearing the 'ref' attribute in the clone.
+ */
+ nsIURI cloneIgnoringRef();
+
+ /**
+ * Clones the current URI, replacing the 'ref' attribute in the clone with
+ * the ref supplied.
+ */
+ nsIURI cloneWithNewRef(in AUTF8String newRef);
+
+ /**
+ * returns a string for the current URI with the ref element cleared.
+ */
+ readonly attribute AUTF8String specIgnoringRef;
+
+ /**
+ * Returns if there is a reference portion (the part after the "#") of the URI.
+ */
+ readonly attribute boolean hasRef;
+};
diff --git a/netwerk/base/nsIURIClassifier.idl b/netwerk/base/nsIURIClassifier.idl
new file mode 100644
index 000000000..a8f6098a7
--- /dev/null
+++ b/netwerk/base/nsIURIClassifier.idl
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * Callback function for nsIURIClassifier lookups.
+ */
+[scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)]
+interface nsIURIClassifierCallback : nsISupports
+{
+ /**
+ * Called by the URI classifier service when it is done checking a URI.
+ *
+ * Clients are responsible for associating callback objects with classify()
+ * calls.
+ *
+ * @param aErrorCode
+ * The error code with which the channel should be cancelled, or
+ * NS_OK if the load should continue normally.
+ */
+ void onClassifyComplete(in nsresult aErrorCode);
+};
+
+/**
+ * The URI classifier service checks a URI against lists of phishing
+ * and malware sites.
+ */
+[scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)]
+interface nsIURIClassifier : nsISupports
+{
+ /**
+ * Classify a Principal using its URI.
+ *
+ * @param aPrincipal
+ * The principal that should be checked by the URI classifier.
+ * @param aTrackingProtectionEnabled
+ * Whether or not to classify the given URI against tracking
+ * protection lists
+ *
+ * @param aCallback
+ * The URI classifier will call this callback when the URI has been
+ * classified.
+ *
+ * @return <code>false</code> if classification is not necessary. The
+ * callback will not be called.
+ * <code>true</code> if classification will be performed. The
+ * callback will be called.
+ */
+ boolean classify(in nsIPrincipal aPrincipal,
+ in boolean aTrackingProtectionEnabled,
+ in nsIURIClassifierCallback aCallback);
+
+ /**
+ * Synchronously classify a URI with a comma-separated string
+ * containing the given tables. This does not make network requests.
+ * The result is a comma-separated string of tables that match.
+ */
+ ACString classifyLocalWithTables(in nsIURI aURI, in ACString aTables);
+};
diff --git a/netwerk/base/nsIURIWithBlobImpl.idl b/netwerk/base/nsIURIWithBlobImpl.idl
new file mode 100644
index 000000000..d055746cc
--- /dev/null
+++ b/netwerk/base/nsIURIWithBlobImpl.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsIURIWithBlobImpl is implemented by URIs which are associated with a
+ * specific BlobImpl.
+ */
+[builtinclass, uuid(331b41d3-3506-4ab5-bef9-aab41e3202a3)]
+interface nsIURIWithBlobImpl : nsISupports
+{
+ /**
+ * The BlobImpl associated with the resource returned when loading this uri.
+ */
+ readonly attribute nsISupports blobImpl;
+};
diff --git a/netwerk/base/nsIURIWithPrincipal.idl b/netwerk/base/nsIURIWithPrincipal.idl
new file mode 100644
index 000000000..6c63aaad9
--- /dev/null
+++ b/netwerk/base/nsIURIWithPrincipal.idl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * nsIURIWithPrincipal is implemented by URIs which are associated with a
+ * specific principal.
+ */
+[scriptable, uuid(626a5c0c-bfd8-4531-8b47-a8451b0daa33)]
+interface nsIURIWithPrincipal : nsISupports
+{
+ /**
+ * The principal associated with the resource returned when loading this
+ * uri.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The uri for the principal.
+ */
+ readonly attribute nsIURI principalUri;
+};
diff --git a/netwerk/base/nsIURIWithQuery.idl b/netwerk/base/nsIURIWithQuery.idl
new file mode 100644
index 000000000..749b2773d
--- /dev/null
+++ b/netwerk/base/nsIURIWithQuery.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.idl"
+
+/**
+ * nsIURIWithQuery is implemented by URIs which have a query parameter.
+ * This is useful for the URL API.
+ */
+[scriptable, uuid(367510ee-8556-435a-8f99-b5fd357e08cc)]
+interface nsIURIWithQuery : nsIURI
+{
+ /**
+ * Returns a path including the directory and file portions of a
+ * URL. For example, the filePath of "http://host/foo/bar.html#baz"
+ * is "/foo/bar.html".
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String filePath;
+
+ /**
+ * Returns the query portion (the part after the "?") of the URL.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String query;
+};
diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl
new file mode 100644
index 000000000..aeaa3f694
--- /dev/null
+++ b/netwerk/base/nsIURL.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURIWithQuery.idl"
+
+/**
+ * The nsIURL interface provides convenience methods that further
+ * break down the path portion of nsIURI:
+ *
+ * http://host/directory/fileBaseName.fileExtension?query
+ * http://host/directory/fileBaseName.fileExtension#ref
+ * \ \ /
+ * \ -----------------------
+ * \ | /
+ * \ fileName /
+ * ----------------------------
+ * |
+ * filePath
+ */
+[scriptable, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)]
+interface nsIURL : nsIURIWithQuery
+{
+ /*************************************************************************
+ * The URL path is broken down into the following principal components:
+ *
+ * attribute AUTF8String filePath;
+ * attribute AUTF8String query;
+ *
+ * These are inherited from nsIURIWithQuery.
+ */
+
+ /*************************************************************************
+ * The URL filepath is broken down into the following sub-components:
+ */
+
+ /**
+ * Returns the directory portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. If the trailing slash is omitted, then the Directory
+ * is /foo/ and the file is bar (i.e. this is a syntactic, not a semantic
+ * breakdown of the Path). And hence don't rely on this for something to
+ * be a definitely be a file. But you can get just the leading directory
+ * portion for sure.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String directory;
+
+ /**
+ * Returns the file name portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. Note that this is purely based on searching for the
+ * last trailing slash. And hence don't rely on this to be a definite file.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileName;
+
+
+ /*************************************************************************
+ * The URL filename is broken down even further:
+ */
+
+ /**
+ * Returns the file basename portion of a filename in a url.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileBaseName;
+
+ /**
+ * Returns the file extension portion of a filename in a url. If a file
+ * extension does not exist, the empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ attribute AUTF8String fileExtension;
+
+ /**
+ * This method takes a uri and compares the two. The common uri portion
+ * is returned as a string. The minimum common uri portion is the
+ * protocol, and any of these if present: login, password, host and port
+ * If no commonality is found, "" is returned. If they are identical, the
+ * whole path with file/ref/etc. is returned. For file uris, it is
+ * expected that the common spec would be at least "file:///" since '/' is
+ * a shared common root.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ ""
+ * 2) http://foo.com/bar/ ftp://foo.com/bar/ ""
+ * 3) http://foo.com:8080/ http://foo.com/bar/ ""
+ * 4) ftp://user@foo.com/ ftp://user:pw@foo.com/ ""
+ * 5) ftp://foo.com/bar/ ftp://foo.com/bar ftp://foo.com/
+ * 6) ftp://foo.com/bar/ ftp://foo.com/bar/b.html ftp://foo.com/bar/
+ * 7) http://foo.com/a.htm#i http://foo.com/b.htm http://foo.com/
+ * 8) ftp://foo.com/c.htm#i ftp://foo.com/c.htm ftp://foo.com/c.htm
+ * 9) file:///a/b/c.html file:///d/e/c.html file:///
+ */
+ AUTF8String getCommonBaseSpec(in nsIURI aURIToCompare);
+
+ /**
+ * This method tries to create a string which specifies the location of the
+ * argument relative to |this|. If the argument and |this| are equal, the
+ * method returns "". If any of the URIs' scheme, host, userpass, or port
+ * don't match, the method returns the full spec of the argument.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ http://www.mozilla.org/
+ * 2) http://mozilla.org/ http://www.mozilla.org http://www.mozilla.org/
+ * 3) http://foo.com/bar/ http://foo.com:80/bar/ ""
+ * 4) http://foo.com/ http://foo.com/a.htm#b a.html#b
+ * 5) http://foo.com/a/b/ http://foo.com/c ../../c
+ */
+ AUTF8String getRelativeSpec(in nsIURI aURIToCompare);
+
+};
diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl
new file mode 100644
index 000000000..3d6ac19b8
--- /dev/null
+++ b/netwerk/base/nsIURLParser.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIURLParser specifies the interface to an URL parser that attempts to
+ * follow the definitions of RFC 2396.
+ */
+[scriptable, uuid(78c5d19f-f5d2-4732-8d3d-d5a7d7133bc0)]
+interface nsIURLParser : nsISupports
+{
+ /**
+ * The string to parse in the following methods may be given as a null
+ * terminated string, in which case the length argument should be -1.
+ *
+ * Out parameters of the following methods are all optional (ie. the caller
+ * may pass-in a NULL value if the corresponding results are not needed).
+ * Signed out parameters may hold a value of -1 if the corresponding result
+ * is not part of the string being parsed.
+ *
+ * The parsing routines attempt to be as forgiving as possible.
+ */
+
+ /**
+ * ParseSpec breaks the URL string up into its 3 major components: a scheme,
+ * an authority section (hostname, etc.), and a path.
+ *
+ * spec = <scheme>://<authority><path>
+ */
+ void parseURL (in string spec, in long specLen,
+ out unsigned long schemePos, out long schemeLen,
+ out unsigned long authorityPos, out long authorityLen,
+ out unsigned long pathPos, out long pathLen);
+
+ /**
+ * ParseAuthority breaks the authority string up into its 4 components:
+ * username, password, hostname, and hostport.
+ *
+ * auth = <username>:<password>@<hostname>:<port>
+ */
+ void parseAuthority (in string authority, in long authorityLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * userinfo = <username>:<password>
+ */
+ void parseUserInfo (in string userinfo, in long userinfoLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen);
+
+ /**
+ * serverinfo = <hostname>:<port>
+ */
+ void parseServerInfo (in string serverinfo, in long serverinfoLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * ParsePath breaks the path string up into its 3 major components: a file path,
+ * a query string, and a reference string.
+ *
+ * path = <filepath>?<query>#<ref>
+ */
+ void parsePath (in string path, in long pathLen,
+ out unsigned long filepathPos, out long filepathLen,
+ out unsigned long queryPos, out long queryLen,
+ out unsigned long refPos, out long refLen);
+
+ /**
+ * ParseFilePath breaks the file path string up into: the directory portion,
+ * file base name, and file extension.
+ *
+ * filepath = <directory><basename>.<extension>
+ */
+ void parseFilePath (in string filepath, in long filepathLen,
+ out unsigned long directoryPos, out long directoryLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+
+ /**
+ * filename = <basename>.<extension>
+ */
+ void parseFileName (in string filename, in long filenameLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+};
+
+%{C++
+// url parser key for use with the category manager
+// mapping from scheme to url parser.
+#define NS_IURLPARSER_KEY "@mozilla.org/urlparser;1"
+%}
diff --git a/netwerk/base/nsIUnicharStreamLoader.idl b/netwerk/base/nsIUnicharStreamLoader.idl
new file mode 100644
index 000000000..d4cdc3fb3
--- /dev/null
+++ b/netwerk/base/nsIUnicharStreamLoader.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIUnicharInputStream;
+interface nsIUnicharStreamLoader;
+interface nsIChannel;
+
+[scriptable, uuid(c2982b39-2e48-429e-92b7-99348a1633c5)]
+interface nsIUnicharStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called as soon as at least 512 octets of data have arrived.
+ * If the stream receives fewer than 512 octets of data in total,
+ * called upon stream completion but before calling OnStreamComplete.
+ * Will not be called if the stream receives no data at all.
+ *
+ * @param aLoader the unichar stream loader
+ * @param aContext the context parameter of the underlying channel
+ * @param aSegment up to 512 octets of raw data from the stream
+ *
+ * @return the name of the character set to be used to decode this stream
+ */
+ ACString onDetermineCharset(in nsIUnicharStreamLoader aLoader,
+ in nsISupports aContext,
+ in ACString aSegment);
+
+ /**
+ * Called when the entire stream has been loaded and decoded.
+ *
+ * @param aLoader the unichar stream loader
+ * @param aContext the context parameter of the underlying channel
+ * @param aStatus the status of the underlying channel
+ * @param aBuffer the contents of the stream, decoded to UTF-16.
+ *
+ * This method will always be called asynchronously by the
+ * nsUnicharIStreamLoader involved, on the thread that called the
+ * loader's init() method. If onDetermineCharset fails,
+ * onStreamComplete will still be called, but aStatus will be an
+ * error code.
+ */
+ void onStreamComplete(in nsIUnicharStreamLoader aLoader,
+ in nsISupports aContext,
+ in nsresult aStatus,
+ in AString aBuffer);
+};
+
+/**
+ * Asynchronously load a channel, converting the data to UTF-16.
+ *
+ * To use this interface, first call init() with a
+ * nsIUnicharStreamLoaderObserver that will be notified when the data has been
+ * loaded. Then call asyncOpen() on the channel with the nsIUnicharStreamLoader
+ * as the listener. The context argument in the asyncOpen() call will be
+ * passed to the onStreamComplete() callback.
+ */
+[scriptable, uuid(afb62060-37c7-4713-8a84-4a0c1199ba5c)]
+interface nsIUnicharStreamLoader : nsIStreamListener
+{
+ /**
+ * Initializes the unichar stream loader
+ *
+ * @param aObserver the observer to notify when a charset is needed and when
+ * the load is complete
+ */
+ void init(in nsIUnicharStreamLoaderObserver aObserver);
+
+ /**
+ * The channel attribute is only valid inside the onDetermineCharset
+ * and onStreamComplete callbacks. Otherwise it will be null.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The charset that onDetermineCharset returned, if that's been
+ * called.
+ */
+ readonly attribute ACString charset;
+
+ /**
+ * Get the raw bytes as seen on the wire prior to character converstion.
+ * Used by Subresource Integrity checker to generate the correct hash.
+ */
+ readonly attribute ACString rawBuffer;
+};
diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl
new file mode 100644
index 000000000..3ec81444a
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIUploadChannel
+ *
+ * A channel may optionally implement this interface if it supports the
+ * notion of uploading a data stream. The upload stream may only be set
+ * prior to the invocation of asyncOpen on the channel.
+ */
+[scriptable, uuid(5cfe15bd-5adb-4a7f-9e55-4f5a67d15794)]
+interface nsIUploadChannel : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * History here is that we need to support both streams that already have
+ * headers (e.g., Content-Type and Content-Length) information prepended to
+ * the stream (by plugins) as well as clients (composer, uploading
+ * application) that want to upload data streams without any knowledge of
+ * protocol specifications. For this reason, we have a special meaning
+ * for the aContentType parameter (see below).
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * If aContentType is empty, the protocol will assume that no
+ * content headers are to be added to the uploaded stream and that
+ * any required headers are already encoded in the stream. In the
+ * case of HTTP, if this parameter is non-empty, then its value will
+ * replace any existing Content-Type header on the HTTP request.
+ * In the case of FTP and FILE, this parameter is ignored.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ */
+ void setUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength);
+
+ /**
+ * Get the stream (to be) uploaded by this channel.
+ */
+ readonly attribute nsIInputStream uploadStream;
+};
diff --git a/netwerk/base/nsIUploadChannel2.idl b/netwerk/base/nsIUploadChannel2.idl
new file mode 100644
index 000000000..2ea74ac35
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIRunnable;
+
+[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)]
+interface nsIUploadChannel2 : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel with the specified
+ * Content-Type and Content-Length header values.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * This value will replace any existing Content-Type
+ * header on the HTTP request, regardless of whether
+ * or not its empty.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ * @param aMethod
+ * The HTTP request method to set on the stream.
+ * @param aStreamHasHeaders
+ * True if the stream already contains headers for the HTTP request.
+ */
+ void explicitSetUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength,
+ in ACString aMethod,
+ in boolean aStreamHasHeaders);
+
+ /**
+ * Value of aStreamHasHeaders from the last successful call to
+ * explicitSetUploadStream. TRUE indicates the attached upload stream
+ * contians request headers.
+ */
+ readonly attribute boolean uploadStreamHasHeaders;
+
+ /**
+ * Ensure the upload stream, if any, is cloneable. This may involve
+ * async copying, so a callback runnable must be provided. It will
+ * invoked on the current thread when the upload stream is ready
+ * for cloning. If the stream is already cloneable, then the callback
+ * will be invoked synchronously.
+ */
+ [noscript]
+ void ensureUploadStreamIsCloneable(in nsIRunnable aCallback);
+
+ /**
+ * Clones the upload stream. May return failure if the upload stream
+ * is not cloneable. If this is not acceptable, use the
+ * ensureUploadStreamIsCloneable() method first.
+ */
+ [noscript]
+ nsIInputStream cloneUploadStream();
+};
diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp
new file mode 100644
index 000000000..42cd6faa5
--- /dev/null
+++ b/netwerk/base/nsIncrementalDownload.cpp
@@ -0,0 +1,936 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+#include "prprf.h"
+#include <algorithm>
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/UniquePtr.h"
+
+// Default values used to initialize a nsIncrementalDownload object.
+#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
+#define DEFAULT_INTERVAL 60 // seconds
+
+#define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
+
+// Number of times to retry a failed byte-range request.
+#define MAX_RETRY_COUNT 20
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+static nsresult
+WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags)
+{
+ PRFileDesc *fd;
+ int32_t mode = 0600;
+ nsresult rv;
+#if defined(MOZ_WIDGET_GONK)
+ // The sdcard on a B2G phone looks like:
+ // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard
+ // On the emulator, xpcshell fails when using 0600 mode to open the file,
+ // and 0660 works.
+ nsCOMPtr<nsIFile> parent;
+ rv = lf->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ uint32_t parentPerm;
+ rv = parent->GetPermissions(&parentPerm);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if ((parentPerm & 0700) == 0) {
+ // Parent directory has no owner-write, so try to use group permissions
+ // instead of owner permissions.
+ mode = 0660;
+ }
+#endif
+ rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (len)
+ rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
+
+ PR_Close(fd);
+ return rv;
+}
+
+static nsresult
+AppendToFile(nsIFile *lf, const char *data, uint32_t len)
+{
+ int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
+ return WriteToFile(lf, data, len, flags);
+}
+
+// maxSize may be -1 if unknown
+static void
+MakeRangeSpec(const int64_t &size, const int64_t &maxSize, int32_t chunkSize,
+ bool fetchRemaining, nsCString &rangeSpec)
+{
+ rangeSpec.AssignLiteral("bytes=");
+ rangeSpec.AppendInt(int64_t(size));
+ rangeSpec.Append('-');
+
+ if (fetchRemaining)
+ return;
+
+ int64_t end = size + int64_t(chunkSize);
+ if (maxSize != int64_t(-1) && end > maxSize)
+ end = maxSize;
+ end -= 1;
+
+ rangeSpec.AppendInt(int64_t(end));
+}
+
+//-----------------------------------------------------------------------------
+
+class nsIncrementalDownload final
+ : public nsIIncrementalDownload
+ , public nsIStreamListener
+ , public nsIObserver
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public nsSupportsWeakReference
+ , public nsIAsyncVerifyRedirectCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINCREMENTALDOWNLOAD
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ nsIncrementalDownload();
+
+private:
+ ~nsIncrementalDownload() {}
+ nsresult FlushChunk();
+ void UpdateProgress();
+ nsresult CallOnStartRequest();
+ void CallOnStopRequest();
+ nsresult StartTimer(int32_t interval);
+ nsresult ProcessTimeout();
+ nsresult ReadCurrentSize();
+ nsresult ClearRequestHeader(nsIHttpChannel *channel);
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsISupports> mObserverContext;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mFinalURI;
+ nsCOMPtr<nsIFile> mDest;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mTimer;
+ mozilla::UniquePtr<char[]> mChunk;
+ int32_t mChunkLen;
+ int32_t mChunkSize;
+ int32_t mInterval;
+ int64_t mTotalSize;
+ int64_t mCurrentSize;
+ uint32_t mLoadFlags;
+ int32_t mNonPartialCount;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mDidOnStartRequest;
+ PRTime mLastProgressUpdate;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+ nsCString mPartialValidator;
+ bool mCacheBust;
+};
+
+nsIncrementalDownload::nsIncrementalDownload()
+ : mChunkLen(0)
+ , mChunkSize(DEFAULT_CHUNK_SIZE)
+ , mInterval(DEFAULT_INTERVAL)
+ , mTotalSize(-1)
+ , mCurrentSize(-1)
+ , mLoadFlags(LOAD_NORMAL)
+ , mNonPartialCount(0)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mDidOnStartRequest(false)
+ , mLastProgressUpdate(0)
+ , mRedirectCallback(nullptr)
+ , mNewRedirectChannel(nullptr)
+ , mCacheBust(false)
+{
+}
+
+nsresult
+nsIncrementalDownload::FlushChunk()
+{
+ NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
+
+ if (mChunkLen == 0)
+ return NS_OK;
+
+ nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mCurrentSize += int64_t(mChunkLen);
+ mChunkLen = 0;
+
+ return NS_OK;
+}
+
+void
+nsIncrementalDownload::UpdateProgress()
+{
+ mLastProgressUpdate = PR_Now();
+
+ if (mProgressSink)
+ mProgressSink->OnProgress(this, mObserverContext,
+ mCurrentSize + mChunkLen,
+ mTotalSize);
+}
+
+nsresult
+nsIncrementalDownload::CallOnStartRequest()
+{
+ if (!mObserver || mDidOnStartRequest)
+ return NS_OK;
+
+ mDidOnStartRequest = true;
+ return mObserver->OnStartRequest(this, mObserverContext);
+}
+
+void
+nsIncrementalDownload::CallOnStopRequest()
+{
+ if (!mObserver)
+ return;
+
+ // Ensure that OnStartRequest is always called once before OnStopRequest.
+ nsresult rv = CallOnStartRequest();
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = rv;
+
+ mIsPending = false;
+
+ mObserver->OnStopRequest(this, mObserverContext, mStatus);
+ mObserver = nullptr;
+ mObserverContext = nullptr;
+}
+
+nsresult
+nsIncrementalDownload::StartTimer(int32_t interval)
+{
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult
+nsIncrementalDownload::ProcessTimeout()
+{
+ NS_ASSERTION(!mChannel, "how can we have a channel?");
+
+ // Handle existing error conditions
+ if (NS_FAILED(mStatus)) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ // Fetch next chunk
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ mFinalURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ this, // aCallbacks
+ mLoadFlags);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ASSERTION(mCurrentSize != int64_t(-1),
+ "we should know the current file size by now");
+
+ rv = ClearRequestHeader(http);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Don't bother making a range request if we are just going to fetch the
+ // entire document.
+ if (mInterval || mCurrentSize != int64_t(0)) {
+ nsAutoCString range;
+ MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
+
+ rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mPartialValidator.IsEmpty())
+ http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"),
+ mPartialValidator, false);
+
+ if (mCacheBust) {
+ http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ }
+ }
+
+ rv = channel->AsyncOpen2(this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Wait to assign mChannel when we know we are going to succeed. This is
+ // important because we don't want to introduce a reference cycle between
+ // mChannel and this until we know for a fact that AsyncOpen has succeeded,
+ // thus ensuring that our stream listener methods will be invoked.
+ mChannel = channel;
+ return NS_OK;
+}
+
+// Reads the current file size and validates it.
+nsresult
+nsIncrementalDownload::ReadCurrentSize()
+{
+ int64_t size;
+ nsresult rv = mDest->GetFileSize((int64_t *) &size);
+ if (rv == NS_ERROR_FILE_NOT_FOUND ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ mCurrentSize = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ mCurrentSize = size;
+ return NS_OK;
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload,
+ nsIIncrementalDownload,
+ nsIRequest,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIObserver,
+ nsIInterfaceRequestor,
+ nsIChannelEventSink,
+ nsISupportsWeakReference,
+ nsIAsyncVerifyRedirectCallback)
+
+// nsIRequest
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetName(nsACString &name)
+{
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+
+ return mURI->GetSpec(name);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::IsPending(bool *isPending)
+{
+ *isPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetStatus(nsresult *status)
+{
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Cancel(nsresult status)
+{
+ NS_ENSURE_ARG(NS_FAILED(status));
+
+ // Ignore this cancelation if we're already canceled.
+ if (NS_FAILED(mStatus))
+ return NS_OK;
+
+ mStatus = status;
+
+ // Nothing more to do if callbacks aren't pending.
+ if (!mIsPending)
+ return NS_OK;
+
+ if (mChannel) {
+ mChannel->Cancel(mStatus);
+ NS_ASSERTION(!mTimer, "what is this timer object doing here?");
+ }
+ else {
+ // dispatch a timer callback event to drive invoking our listener's
+ // OnStopRequest.
+ if (mTimer)
+ mTimer->Cancel();
+ StartTimer(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Suspend()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Resume()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
+{
+ *loadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
+{
+ mLoadFlags = loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIIncrementalDownload
+
+NS_IMETHODIMP
+nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
+ int32_t chunkSize, int32_t interval)
+{
+ // Keep it simple: only allow initialization once
+ NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
+
+ mDest = do_QueryInterface(dest);
+ NS_ENSURE_ARG(mDest);
+
+ mURI = uri;
+ mFinalURI = uri;
+
+ if (chunkSize > 0)
+ mChunkSize = chunkSize;
+ if (interval >= 0)
+ mInterval = interval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetURI(nsIURI **result)
+{
+ NS_IF_ADDREF(*result = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetFinalURI(nsIURI **result)
+{
+ NS_IF_ADDREF(*result = mFinalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetDestination(nsIFile **result)
+{
+ if (!mDest) {
+ *result = nullptr;
+ return NS_OK;
+ }
+ // Return a clone of mDest so that callers may modify the resulting nsIFile
+ // without corrupting our internal object. This also works around the fact
+ // that some nsIFile impls may cache the result of stat'ing the filesystem.
+ return mDest->Clone(result);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTotalSize(int64_t *result)
+{
+ *result = mTotalSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetCurrentSize(int64_t *result)
+{
+ *result = mCurrentSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Start(nsIRequestObserver *observer,
+ nsISupports *context)
+{
+ NS_ENSURE_ARG(observer);
+ NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
+
+ // Observe system shutdown so we can be sure to release any reference held
+ // between ourselves and the timer. We have the observer service hold a weak
+ // reference to us, so that we don't have to worry about calling
+ // RemoveObserver. XXX(darin): The timer code should do this for us.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs)
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+
+ nsresult rv = ReadCurrentSize();
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = StartTimer(0);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mObserver = observer;
+ mObserverContext = context;
+ mProgressSink = do_QueryInterface(observer); // ok if null
+
+ mIsPending = true;
+ return NS_OK;
+}
+
+// nsIRequestObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ensure that we are receiving a 206 response.
+ uint32_t code;
+ rv = http->GetResponseStatus(&code);
+ if (NS_FAILED(rv))
+ return rv;
+ if (code != 206) {
+ // We may already have the entire file downloaded, in which case
+ // our request for a range beyond the end of the file would have
+ // been met with an error response code.
+ if (code == 416 && mTotalSize == int64_t(-1)) {
+ mTotalSize = mCurrentSize;
+ // Return an error code here to suppress OnDataAvailable.
+ return NS_ERROR_DOWNLOAD_COMPLETE;
+ }
+ // The server may have decided to give us all of the data in one chunk. If
+ // we requested a partial range, then we don't want to download all of the
+ // data at once. So, we'll just try again, but if this keeps happening then
+ // we'll eventually give up.
+ if (code == 200) {
+ if (mInterval) {
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ // Since we have been asked to download the rest of the file, we can deal
+ // with a 200 response. This may result in downloading the beginning of
+ // the file again, but that can't really be helped.
+ } else {
+ NS_WARNING("server response was unexpected");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // We got a partial response, so clear this counter in case the next chunk
+ // results in a 200 response.
+ mNonPartialCount = 0;
+
+ // confirm that the content-range response header is consistent with
+ // expectations on each 206. If it is not then drop this response and
+ // retry with no-cache set.
+ if (!mCacheBust) {
+ nsAutoCString buf;
+ int64_t startByte = 0;
+ bool confirmedOK = false;
+
+ rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
+ if (NS_FAILED(rv))
+ return rv; // it isn't a useful 206 without a CONTENT-RANGE of some sort
+
+ // Content-Range: bytes 0-299999/25604694
+ int32_t p = buf.Find("bytes ");
+
+ // first look for the starting point of the content-range
+ // to make sure it is what we expect
+ if (p != -1) {
+ char *endptr = nullptr;
+ const char *s = buf.get() + p + 6;
+ while (*s && *s == ' ')
+ s++;
+ startByte = strtol(s, &endptr, 10);
+
+ if (*s && endptr && (endptr != s) &&
+ (mCurrentSize == startByte)) {
+
+ // ok the starting point is confirmed. We still need to check the
+ // total size of the range for consistency if this isn't
+ // the first chunk
+ if (mTotalSize == int64_t(-1)) {
+ // first chunk
+ confirmedOK = true;
+ } else {
+ int32_t slash = buf.FindChar('/');
+ int64_t rangeSize = 0;
+ if (slash != kNotFound &&
+ (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &rangeSize) == 1) &&
+ rangeSize == mTotalSize) {
+ confirmedOK = true;
+ }
+ }
+ }
+ }
+
+ if (!confirmedOK) {
+ NS_WARNING("unexpected content-range");
+ mCacheBust = true;
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ }
+ }
+
+ // Do special processing after the first response.
+ if (mTotalSize == int64_t(-1)) {
+ // Update knowledge of mFinalURI
+ rv = http->GetURI(getter_AddRefs(mFinalURI));
+ if (NS_FAILED(rv))
+ return rv;
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator);
+ if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/")))
+ mPartialValidator.Truncate(); // don't use weak validators
+ if (mPartialValidator.IsEmpty())
+ http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator);
+
+ if (code == 206) {
+ // OK, read the Content-Range header to determine the total size of this
+ // download file.
+ nsAutoCString buf;
+ rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
+ if (NS_FAILED(rv))
+ return rv;
+ int32_t slash = buf.FindChar('/');
+ if (slash == kNotFound) {
+ NS_WARNING("server returned invalid Content-Range header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t *) &mTotalSize) != 1)
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ rv = http->GetContentLength(&mTotalSize);
+ if (NS_FAILED(rv))
+ return rv;
+ // We need to know the total size of the thing we're trying to download.
+ if (mTotalSize == int64_t(-1)) {
+ NS_WARNING("server returned no content-length header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Need to truncate (or create, if it doesn't exist) the file since we
+ // are downloading the whole thing.
+ WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
+ mCurrentSize = 0;
+ }
+
+ // Notify observer that we are starting...
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
+ int64_t diff = mTotalSize - mCurrentSize;
+ if (diff <= int64_t(0)) {
+ NS_WARNING("about to set a bogus chunk size; giving up");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (diff < int64_t(mChunkSize))
+ mChunkSize = uint32_t(diff);
+
+ mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
+ if (!mChunk)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ // Not a real error; just a trick to kill off the channel without our
+ // listener having to care.
+ if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
+ return NS_OK;
+
+ // Not a real error; just a trick used to suppress OnDataAvailable calls.
+ if (status == NS_ERROR_DOWNLOAD_COMPLETE)
+ status = NS_OK;
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ if (mChunk) {
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = FlushChunk();
+
+ mChunk = nullptr; // deletes memory
+ mChunkLen = 0;
+ UpdateProgress();
+ }
+
+ mChannel = nullptr;
+
+ // Notify listener if we hit an error or finished
+ if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ return StartTimer(mInterval); // Do next chunk
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ while (count) {
+ uint32_t space = mChunkSize - mChunkLen;
+ uint32_t n, len = std::min(space, count);
+
+ nsresult rv = input->Read(&mChunk[mChunkLen], len, &n);
+ if (NS_FAILED(rv))
+ return rv;
+ if (n != len)
+ return NS_ERROR_UNEXPECTED;
+
+ count -= n;
+ mChunkLen += n;
+
+ if (mChunkLen == mChunkSize) {
+ rv = FlushChunk();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
+ UpdateProgress();
+
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ Cancel(NS_ERROR_ABORT);
+
+ // Since the app is shutting down, we need to go ahead and notify our
+ // observer here. Otherwise, we would notify them after XPCOM has been
+ // shutdown or not at all.
+ CallOnStopRequest();
+ }
+ else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
+ mTimer = nullptr;
+ nsresult rv = ProcessTimeout();
+ if (NS_FAILED(rv))
+ Cancel(rv);
+ }
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
+{
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
+ if (ir)
+ return ir->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult
+nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
+{
+ NS_ENSURE_ARG(channel);
+
+ // We don't support encodings -- they make the Content-Length not equal
+ // to the actual size of the data.
+ return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
+ NS_LITERAL_CSTRING(""), false);
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ // In response to a redirect, we need to propagate the Range header. See bug
+ // 311595. Any failure code returned from this function aborts the redirect.
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
+ NS_ENSURE_STATE(http);
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
+ NS_ENSURE_STATE(newHttpChannel);
+
+ NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
+
+ nsresult rv = ClearRequestHeader(newHttpChannel);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If we didn't have a Range header, then we must be doing a full download.
+ nsAutoCString rangeVal;
+ http->GetRequestHeader(rangeHdr, rangeVal);
+ if (!rangeVal.IsEmpty()) {
+ rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // A redirection changes the validator
+ mPartialValidator.Truncate();
+
+ if (mCacheBust) {
+ newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"),
+ NS_LITERAL_CSTRING("no-cache"), false);
+ }
+
+ // Prepare to receive callback
+ mRedirectCallback = cb;
+ mNewRedirectChannel = newChannel;
+
+ // Give the observer a chance to see this redirect notification.
+ nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
+ if (sink) {
+ rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ }
+ return rv;
+ }
+ (void) OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
+{
+ NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
+
+ // Update mChannel, so we can Cancel the new channel.
+ if (NS_SUCCEEDED(result))
+ mChannel = mNewRedirectChannel;
+
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+extern nsresult
+net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
+{
+ if (outer)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsIncrementalDownload *d = new nsIncrementalDownload();
+ if (!d)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(d);
+ nsresult rv = d->QueryInterface(iid, result);
+ NS_RELEASE(d);
+ return rv;
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp
new file mode 100644
index 000000000..a7298be3f
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIncrementalStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "GeckoProfiler.h"
+
+#include <limits>
+
+nsIncrementalStreamLoader::nsIncrementalStreamLoader()
+ : mData(), mBytesConsumed(0)
+{
+}
+
+nsIncrementalStreamLoader::~nsIncrementalStreamLoader()
+{
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer)
+{
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = observer;
+ return NS_OK;
+}
+
+nsresult
+nsIncrementalStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsIncrementalStreamLoader* it = new nsIncrementalStreamLoader();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes)
+{
+ *aNumBytes = mBytesConsumed + mData.length();
+ return NS_OK;
+}
+
+/* readonly attribute nsIRequest request; */
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetRequest(nsIRequest **aRequest)
+{
+ NS_IF_ADDREF(*aRequest = mRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) );
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ if (uint64_t(contentLength) > std::numeric_limits<size_t>::max()) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ mContext = ctxt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ PROFILER_LABEL("nsIncrementalStreamLoader", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv = mObserver->OnStreamComplete(this, mContext, aStatus,
+ length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ mContext = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsIncrementalStreamLoader::WriteSegmentFun(nsIInputStream *inStr,
+ void *closure,
+ const char *fromSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsIncrementalStreamLoader *self = (nsIncrementalStreamLoader *) closure;
+
+ const uint8_t *data = reinterpret_cast<const uint8_t *>(fromSegment);
+ uint32_t consumedCount = 0;
+ nsresult rv;
+ if (self->mData.empty()) {
+ // Shortcut when observer wants to keep the listener's buffer empty.
+ rv = self->mObserver->OnIncrementalData(self, self->mContext,
+ count, data, &consumedCount);
+
+ if (rv != NS_OK) {
+ return rv;
+ }
+
+ if (consumedCount > count) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount < count) {
+ if (!self->mData.append(fromSegment + consumedCount,
+ count - consumedCount)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ } else {
+ // We have some non-consumed data from previous OnIncrementalData call,
+ // appending new data and reporting combined data.
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ size_t length = self->mData.length();
+ uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length;
+ uint8_t* elems = self->mData.extractOrCopyRawBuffer();
+
+ rv = self->mObserver->OnIncrementalData(self, self->mContext,
+ reportCount, elems, &consumedCount);
+
+ // We still own elems, freeing its memory when exiting scope.
+ if (rv != NS_OK) {
+ free(elems);
+ return rv;
+ }
+
+ if (consumedCount > reportCount) {
+ free(elems);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount == length) {
+ free(elems); // good case -- fully consumed data
+ } else {
+ // Adopting elems back (at least its portion).
+ self->mData.replaceRawBuffer(elems, length);
+ if (consumedCount > 0) {
+ self->mData.erase(self->mData.begin() + consumedCount);
+ }
+ }
+ }
+
+ self->mBytesConsumed += consumedCount;
+ *writeCount = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ }
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ mRequest = nullptr;
+ return rv;
+}
+
+void
+nsIncrementalStreamLoader::ReleaseData()
+{
+ mData.clearAndFree();
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::CheckListenerChain()
+{
+ return NS_OK;
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h
new file mode 100644
index 000000000..f04d4a958
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncrementalStreamLoader_h__
+#define nsIncrementalStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+class nsIncrementalStreamLoader final : public nsIIncrementalStreamLoader
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsIncrementalStreamLoader();
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsIncrementalStreamLoader();
+
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIIncrementalStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+
+ // Number of consumed bytes from the mData.
+ size_t mBytesConsumed;
+};
+
+#endif // nsIncrementalStreamLoader_h__
diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp
new file mode 100644
index 000000000..99877eebf
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel
+
+namespace mozilla {
+namespace net {
+
+nsresult
+nsInputStreamChannel::OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel)
+{
+ NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED);
+
+ // If content length is unknown, then we must guess. In this case, we assume
+ // the stream can tell us. If the stream is a pipe, then this will not work.
+
+ if (mContentLength < 0) {
+ uint64_t avail;
+ nsresult rv = mContentStream->Available(&avail);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // This just means there's nothing in the stream
+ avail = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mContentLength = avail;
+ }
+
+ EnableSynthesizedProgressEvents(true);
+
+ NS_ADDREF(*result = mContentStream);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamChannel,
+ nsBaseChannel,
+ nsIInputStreamChannel)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsIInputStreamChannel
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetURI(nsIURI *uri)
+{
+ NS_ENSURE_TRUE(!URI(), NS_ERROR_ALREADY_INITIALIZED);
+ nsBaseChannel::SetURI(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetContentStream(nsIInputStream **stream)
+{
+ NS_IF_ADDREF(*stream = mContentStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetContentStream(nsIInputStream *stream)
+{
+ NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED);
+ mContentStream = stream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData)
+{
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData)
+{
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocChannel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetIsSrcdocChannel(bool *aIsSrcdocChannel)
+{
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI)
+{
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI)
+{
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h
new file mode 100644
index 000000000..3c1c6e83b
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamChannel_h__
+#define nsInputStreamChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsInputStreamChannel : public nsBaseChannel
+ , public nsIInputStreamChannel
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCHANNEL
+
+ nsInputStreamChannel() :
+ mIsSrcdocChannel(false) {}
+
+protected:
+ virtual ~nsInputStreamChannel() {}
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel) override;
+
+ virtual void OnChannelDone() override {
+ mContentStream = nullptr;
+ }
+
+private:
+ nsCOMPtr<nsIInputStream> mContentStream;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsString mSrcdocData;
+ bool mIsSrcdocChannel;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp
new file mode 100644
index 000000000..19c2a790a
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -0,0 +1,766 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sts=4 sw=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsInputStreamPump.h"
+#include "nsIStreamTransportService.h"
+#include "nsISeekableStream.h"
+#include "nsITransport.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "GeckoProfiler.h"
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsNetCID.h"
+#include <algorithm>
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+//
+// MOZ_LOG=nsStreamPump:5
+//
+static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump");
+#undef LOG
+#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump methods
+//-----------------------------------------------------------------------------
+
+nsInputStreamPump::nsInputStreamPump()
+ : mState(STATE_IDLE)
+ , mStreamOffset(0)
+ , mStreamLength(UINT64_MAX)
+ , mStatus(NS_OK)
+ , mSuspendCount(0)
+ , mLoadFlags(LOAD_NORMAL)
+ , mProcessingCallbacks(false)
+ , mWaitingForInputStreamReady(false)
+ , mCloseWhenDone(false)
+ , mRetargeting(false)
+ , mMonitor("nsInputStreamPump")
+{
+}
+
+nsInputStreamPump::~nsInputStreamPump()
+{
+}
+
+nsresult
+nsInputStreamPump::Create(nsInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos,
+ int64_t streamLen,
+ uint32_t segsize,
+ uint32_t segcount,
+ bool closeWhenDone)
+{
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<nsInputStreamPump> pump = new nsInputStreamPump();
+ if (pump) {
+ rv = pump->Init(stream, streamPos, streamLen,
+ segsize, segcount, closeWhenDone);
+ if (NS_SUCCEEDED(rv)) {
+ pump.forget(result);
+ }
+ }
+ return rv;
+}
+
+struct PeekData {
+ PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure)
+ : mFunc(fun), mClosure(closure) {}
+
+ nsInputStreamPump::PeekSegmentFun mFunc;
+ void* mClosure;
+};
+
+static nsresult
+CallPeekFunc(nsIInputStream *aInStream, void *aClosure,
+ const char *aFromSegment, uint32_t aToOffset, uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ NS_ASSERTION(aToOffset == 0, "Called more than once?");
+ NS_ASSERTION(aCount > 0, "Called without data?");
+
+ PeekData* data = static_cast<PeekData*>(aClosure);
+ data->mFunc(data->mClosure,
+ reinterpret_cast<const uint8_t*>(aFromSegment), aCount);
+ return NS_BINDING_ABORTED;
+}
+
+nsresult
+nsInputStreamPump::PeekStream(PeekSegmentFun callback, void* closure)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ASSERTION(mAsyncStream, "PeekStream called without stream");
+
+ // See if the pipe is closed by checking the return of Available.
+ uint64_t dummy64;
+ nsresult rv = mAsyncStream->Available(&dummy64);
+ if (NS_FAILED(rv))
+ return rv;
+ uint32_t dummy = (uint32_t)std::min(dummy64, (uint64_t)UINT32_MAX);
+
+ PeekData data(callback, closure);
+ return mAsyncStream->ReadSegments(CallPeekFunc,
+ &data,
+ nsIOService::gDefaultSegmentSize,
+ &dummy);
+}
+
+nsresult
+nsInputStreamPump::EnsureWaiting()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ // no need to worry about multiple threads... an input stream pump lives
+ // on only one thread at a time.
+ MOZ_ASSERT(mAsyncStream);
+ if (!mWaitingForInputStreamReady && !mProcessingCallbacks) {
+ // Ensure OnStateStop is called on the main thread.
+ if (mState == STATE_STOP) {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (mTargetThread != mainThread) {
+ mTargetThread = do_QueryInterface(mainThread);
+ }
+ }
+ MOZ_ASSERT(mTargetThread);
+ nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("AsyncWait failed");
+ return rv;
+ }
+ // Any retargeting during STATE_START or START_TRANSFER is complete
+ // after the call to AsyncWait; next callback wil be on mTargetThread.
+ mRetargeting = false;
+ mWaitingForInputStreamReady = true;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsISupports
+//-----------------------------------------------------------------------------
+
+// although this class can only be accessed from one thread at a time, we do
+// allow its ownership to move from thread to thread, assuming the consumer
+// understands the limitations of this.
+NS_IMPL_ISUPPORTS(nsInputStreamPump,
+ nsIRequest,
+ nsIThreadRetargetableRequest,
+ nsIInputStreamCallback,
+ nsIInputStreamPump)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::GetName(nsACString &result)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::IsPending(bool *result)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *result = (mState != STATE_IDLE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetStatus(nsresult *status)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Cancel(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Cancel [this=%p status=%x]\n",
+ this, status));
+
+ if (NS_FAILED(mStatus)) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(NS_FAILED(status), "cancel with non-failure status code");
+ mStatus = status;
+
+ // close input stream
+ if (mAsyncStream) {
+ mAsyncStream->CloseWithStatus(status);
+ if (mSuspendCount == 0)
+ EnsureWaiting();
+ // Otherwise, EnsureWaiting will be called by Resume().
+ // Note that while suspended, OnInputStreamReady will
+ // not do anything, and also note that calling asyncWait
+ // on a closed stream works and will dispatch an event immediately.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Suspend()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED);
+ ++mSuspendCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Resume()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ LOG(("nsInputStreamPump::Resume [this=%p]\n", this));
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED);
+
+ if (--mSuspendCount == 0)
+ EnsureWaiting();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamPump implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::Init(nsIInputStream *stream,
+ int64_t streamPos, int64_t streamLen,
+ uint32_t segsize, uint32_t segcount,
+ bool closeWhenDone)
+{
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+
+ mStreamOffset = uint64_t(streamPos);
+ if (int64_t(streamLen) >= int64_t(0))
+ mStreamLength = uint64_t(streamLen);
+ mStream = stream;
+ mSegSize = segsize;
+ mSegCount = segcount;
+ mCloseWhenDone = closeWhenDone;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::AsyncRead(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+ MOZ_ASSERT(NS_IsMainThread(), "nsInputStreamPump should be read from the "
+ "main thread only.");
+
+ //
+ // OK, we need to use the stream transport service if
+ //
+ // (1) the stream is blocking
+ // (2) the stream does not support nsIAsyncInputStream
+ //
+
+ bool nonBlocking;
+ nsresult rv = mStream->IsNonBlocking(&nonBlocking);
+ if (NS_FAILED(rv)) return rv;
+
+ if (nonBlocking) {
+ mAsyncStream = do_QueryInterface(mStream);
+ //
+ // if the stream supports nsIAsyncInputStream, and if we need to seek
+ // to a starting offset, then we must do so here. in the non-async
+ // stream case, the stream transport service will take care of seeking
+ // for us.
+ //
+ if (mAsyncStream && (mStreamOffset != UINT64_MAX)) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mStreamOffset);
+ }
+ }
+
+ if (!mAsyncStream) {
+ // ok, let's use the stream transport service to read this stream.
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateInputTransport(mStream, mStreamOffset, mStreamLength,
+ mCloseWhenDone, getter_AddRefs(transport));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStream> wrapper;
+ rv = transport->OpenInputStream(0, mSegSize, mSegCount, getter_AddRefs(wrapper));
+ if (NS_FAILED(rv)) return rv;
+
+ mAsyncStream = do_QueryInterface(wrapper, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // release our reference to the original stream. from this point forward,
+ // we only reference the "stream" via mAsyncStream.
+ mStream = nullptr;
+
+ // mStreamOffset now holds the number of bytes currently read. we use this
+ // to enforce the mStreamLength restriction.
+ mStreamOffset = 0;
+
+ // grab event queue (we must do this here by contract, since all notifications
+ // must go to the thread which called AsyncRead)
+ mTargetThread = do_GetCurrentThread();
+ NS_ENSURE_STATE(mTargetThread);
+
+ rv = EnsureWaiting();
+ if (NS_FAILED(rv)) return rv;
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ mState = STATE_START;
+ mListener = listener;
+ mListenerContext = ctxt;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)
+{
+ LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this));
+
+ PROFILER_LABEL("nsInputStreamPump", "OnInputStreamReady",
+ js::ProfileEntry::Category::NETWORK);
+
+ // this function has been called from a PLEvent, so we can safely call
+ // any listener or progress sink methods directly from here.
+
+ for (;;) {
+ // There should only be one iteration of this loop happening at a time.
+ // To prevent AsyncWait() (called during callbacks or on other threads)
+ // from creating a parallel OnInputStreamReady(), we use:
+ // -- a monitor; and
+ // -- a boolean mProcessingCallbacks to detect parallel loops
+ // when exiting the monitor for callbacks.
+ ReentrantMonitorAutoEnter lock(mMonitor);
+
+ // Prevent parallel execution during callbacks, while out of monitor.
+ if (mProcessingCallbacks) {
+ MOZ_ASSERT(!mProcessingCallbacks);
+ break;
+ }
+ mProcessingCallbacks = true;
+ if (mSuspendCount || mState == STATE_IDLE) {
+ mWaitingForInputStreamReady = false;
+ mProcessingCallbacks = false;
+ break;
+ }
+
+ uint32_t nextState;
+ switch (mState) {
+ case STATE_START:
+ nextState = OnStateStart();
+ break;
+ case STATE_TRANSFER:
+ nextState = OnStateTransfer();
+ break;
+ case STATE_STOP:
+ mRetargeting = false;
+ nextState = OnStateStop();
+ break;
+ default:
+ nextState = 0;
+ NS_NOTREACHED("Unknown enum value.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stillTransferring = (mState == STATE_TRANSFER &&
+ nextState == STATE_TRANSFER);
+ if (stillTransferring) {
+ NS_ASSERTION(NS_SUCCEEDED(mStatus),
+ "Should not have failed status for ongoing transfer");
+ } else {
+ NS_ASSERTION(mState != nextState,
+ "Only OnStateTransfer can be called more than once.");
+ }
+ if (mRetargeting) {
+ NS_ASSERTION(mState != STATE_STOP,
+ "Retargeting should not happen during OnStateStop.");
+ }
+
+ // Set mRetargeting so EnsureWaiting will be called. It ensures that
+ // OnStateStop is called on the main thread.
+ if (nextState == STATE_STOP && !NS_IsMainThread()) {
+ mRetargeting = true;
+ }
+
+ // Unset mProcessingCallbacks here (while we have lock) so our own call to
+ // EnsureWaiting isn't blocked by it.
+ mProcessingCallbacks = false;
+
+ // We must break the loop when we're switching event delivery to another
+ // thread and the input stream pump is suspended, otherwise
+ // OnStateStop() might be called off the main thread. See bug 1026951
+ // comment #107 for the exact scenario.
+ if (mSuspendCount && mRetargeting) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ break;
+ }
+
+ // Wait asynchronously if there is still data to transfer, or we're
+ // switching event delivery to another thread.
+ if (!mSuspendCount && (stillTransferring || mRetargeting)) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ nsresult rv = EnsureWaiting();
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // Failure to start asynchronous wait: stop transfer.
+ // Do not set mStatus if it was previously set to report a failure.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ nextState = STATE_STOP;
+ }
+
+ mState = nextState;
+ }
+ return NS_OK;
+}
+
+uint32_t
+nsInputStreamPump::OnStateStart()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateStart",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateStart [this=%p]\n", this));
+
+ nsresult rv;
+
+ // need to check the reason why the stream is ready. this is required
+ // so our listener can check our status from OnStartRequest.
+ // XXX async streams should have a GetStatus method!
+ if (NS_SUCCEEDED(mStatus)) {
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_CLOSED)
+ mStatus = rv;
+ }
+
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ rv = mListener->OnStartRequest(this, mListenerContext);
+ mMonitor.Enter();
+ }
+
+ // an error returned from OnStartRequest should cause us to abort; however,
+ // we must not stomp on mStatus if already canceled.
+ if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus))
+ mStatus = rv;
+
+ return NS_SUCCEEDED(mStatus) ? STATE_TRANSFER : STATE_STOP;
+}
+
+uint32_t
+nsInputStreamPump::OnStateTransfer()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateTransfer",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateTransfer [this=%p]\n", this));
+
+ // if canceled, go directly to STATE_STOP...
+ if (NS_FAILED(mStatus))
+ return STATE_STOP;
+
+ nsresult rv;
+
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ LOG((" Available returned [stream=%x rv=%x avail=%llu]\n", mAsyncStream.get(), rv, avail));
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ avail = 0;
+ }
+ else if (NS_SUCCEEDED(rv) && avail) {
+ // figure out how much data to report (XXX detect overflow??)
+ if (avail > mStreamLength - mStreamOffset)
+ avail = mStreamLength - mStreamOffset;
+
+ if (avail) {
+ // we used to limit avail to 16K - we were afraid some ODA handlers
+ // might assume they wouldn't get more than 16K at once
+ // we're removing that limit since it speeds up local file access.
+ // Now there's an implicit 64K limit of 4 16K segments
+ // NOTE: ok, so the story is as follows. OnDataAvailable impls
+ // are by contract supposed to consume exactly |avail| bytes.
+ // however, many do not... mailnews... stream converters...
+ // cough, cough. the input stream pump is fairly tolerant
+ // in this regard; however, if an ODA does not consume any
+ // data from the stream, then we could potentially end up in
+ // an infinite loop. we do our best here to try to catch
+ // such an error. (see bug 189672)
+
+ // in most cases this QI will succeed (mAsyncStream is almost always
+ // a nsPipeInputStream, which implements nsISeekableStream::Tell).
+ int64_t offsetBefore;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mAsyncStream);
+ if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
+ NS_NOTREACHED("Tell failed on readable stream");
+ offsetBefore = 0;
+ }
+
+ uint32_t odaAvail =
+ avail > UINT32_MAX ?
+ UINT32_MAX : uint32_t(avail);
+
+ LOG((" calling OnDataAvailable [offset=%llu count=%llu(%u)]\n",
+ mStreamOffset, avail, odaAvail));
+
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ rv = mListener->OnDataAvailable(this, mListenerContext,
+ mAsyncStream, mStreamOffset,
+ odaAvail);
+ mMonitor.Enter();
+ }
+
+ // don't enter this code if ODA failed or called Cancel
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) {
+ // test to see if this ODA failed to consume data
+ if (seekable) {
+ // NOTE: if Tell fails, which can happen if the stream is
+ // now closed, then we assume that everything was read.
+ int64_t offsetAfter;
+ if (NS_FAILED(seekable->Tell(&offsetAfter)))
+ offsetAfter = offsetBefore + odaAvail;
+ if (offsetAfter > offsetBefore)
+ mStreamOffset += (offsetAfter - offsetBefore);
+ else if (mSuspendCount == 0) {
+ //
+ // possible infinite loop if we continue pumping data!
+ //
+ // NOTE: although not allowed by nsIStreamListener, we
+ // will allow the ODA impl to Suspend the pump. IMAP
+ // does this :-(
+ //
+ NS_ERROR("OnDataAvailable implementation consumed no data");
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else
+ mStreamOffset += odaAvail; // assume ODA behaved well
+ }
+ }
+ }
+
+ // an error returned from Available or OnDataAvailable should cause us to
+ // abort; however, we must not stomp on mStatus if already canceled.
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_FAILED(rv))
+ mStatus = rv;
+ else if (avail) {
+ // if stream is now closed, advance to STATE_STOP right away.
+ // Available may return 0 bytes available at the moment; that
+ // would not mean that we are done.
+ // XXX async streams should have a GetStatus method!
+ rv = mAsyncStream->Available(&avail);
+ if (NS_SUCCEEDED(rv))
+ return STATE_TRANSFER;
+ if (rv != NS_BASE_STREAM_CLOSED)
+ mStatus = rv;
+ }
+ }
+ return STATE_STOP;
+}
+
+nsresult
+nsInputStreamPump::CallOnStateStop()
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CallOnStateStop should only be called on the main thread.");
+
+ mState = OnStateStop();
+ return NS_OK;
+}
+
+uint32_t
+nsInputStreamPump::OnStateStop()
+{
+ mMonitor.AssertCurrentThreadIn();
+
+ if (!NS_IsMainThread()) {
+ // Hopefully temporary hack: OnStateStop should only run on the main
+ // thread, but we're seeing some rare off-main-thread calls. For now
+ // just redispatch to the main thread in release builds, and crash in
+ // debug builds.
+ MOZ_ASSERT(NS_IsMainThread(),
+ "OnStateStop should only be called on the main thread.");
+ nsresult rv = NS_DispatchToMainThread(
+ NewRunnableMethod(this, &nsInputStreamPump::CallOnStateStop));
+ NS_ENSURE_SUCCESS(rv, STATE_IDLE);
+ return STATE_IDLE;
+ }
+
+ PROFILER_LABEL("nsInputStreamPump", "OnStateStop",
+ js::ProfileEntry::Category::NETWORK);
+
+ LOG((" OnStateStop [this=%p status=%x]\n", this, mStatus));
+
+ // if an error occurred, we must be sure to pass the error onto the async
+ // stream. in some cases, this is redundant, but since close is idempotent,
+ // this is OK. otherwise, be sure to honor the "close-when-done" option.
+
+ if (!mAsyncStream || !mListener) {
+ MOZ_ASSERT(mAsyncStream, "null mAsyncStream: OnStateStop called twice?");
+ MOZ_ASSERT(mListener, "null mListener: OnStateStop called twice?");
+ return STATE_IDLE;
+ }
+
+ if (NS_FAILED(mStatus))
+ mAsyncStream->CloseWithStatus(mStatus);
+ else if (mCloseWhenDone)
+ mAsyncStream->Close();
+
+ mAsyncStream = nullptr;
+ mTargetThread = nullptr;
+ mIsPending = false;
+ {
+ // Note: Must exit monitor for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ mMonitor.Exit();
+ mListener->OnStopRequest(this, mListenerContext, mStatus);
+ mMonitor.Enter();
+ }
+ mListener = nullptr;
+ mListenerContext = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return STATE_IDLE;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
+{
+ ReentrantMonitorAutoEnter mon(mMonitor);
+
+ NS_ENSURE_ARG(aNewTarget);
+ NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER,
+ NS_ERROR_UNEXPECTED);
+
+ // If canceled, do not retarget. Return with canceled status.
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (aNewTarget == mTargetThread) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (NS_SUCCEEDED(rv) && retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_SUCCEEDED(rv)) {
+ mTargetThread = aNewTarget;
+ mRetargeting = true;
+ }
+ }
+ LOG(("nsInputStreamPump::RetargetDeliveryTo [this=%x aNewTarget=%p] "
+ "%s listener [%p] rv[%x]",
+ this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"),
+ (nsIStreamListener*)mListener, rv));
+ return rv;
+}
diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h
new file mode 100644
index 000000000..c9b7bd64c
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamPump_h__
+#define nsInputStreamPump_h__
+
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrantMonitor.h"
+
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+
+class nsInputStreamPump final : public nsIInputStreamPump
+ , public nsIInputStreamCallback
+ , public nsIThreadRetargetableRequest
+{
+ ~nsInputStreamPump();
+
+public:
+ typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINPUTSTREAMPUMP
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+
+ nsInputStreamPump();
+
+ static nsresult
+ Create(nsInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos = -1,
+ int64_t streamLen = -1,
+ uint32_t segsize = 0,
+ uint32_t segcount = 0,
+ bool closeWhenDone = false);
+
+ typedef void (*PeekSegmentFun)(void *closure, const uint8_t *buf,
+ uint32_t bufLen);
+ /**
+ * Peek into the first chunk of data that's in the stream. Note that this
+ * method will not call the callback when there is no data in the stream.
+ * The callback will be called at most once.
+ *
+ * The data from the stream will not be consumed, i.e. the pump's listener
+ * can still read all the data.
+ *
+ * Do not call before asyncRead. Do not call after onStopRequest.
+ */
+ nsresult PeekStream(PeekSegmentFun callback, void *closure);
+
+ /**
+ * Dispatched (to the main thread) by OnStateStop if it's called off main
+ * thread. Updates mState based on return value of OnStateStop.
+ */
+ nsresult CallOnStateStop();
+
+protected:
+
+ enum {
+ STATE_IDLE,
+ STATE_START,
+ STATE_TRANSFER,
+ STATE_STOP
+ };
+
+ nsresult EnsureWaiting();
+ uint32_t OnStateStart();
+ uint32_t OnStateTransfer();
+ uint32_t OnStateStop();
+
+ uint32_t mState;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+ uint64_t mStreamOffset;
+ uint64_t mStreamLength;
+ uint32_t mSegSize;
+ uint32_t mSegCount;
+ nsresult mStatus;
+ uint32_t mSuspendCount;
+ uint32_t mLoadFlags;
+ bool mIsPending;
+ // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer
+ // and OnStateStop. Used to prevent calls to AsyncWait during callbacks.
+ bool mProcessingCallbacks;
+ // True if waiting on the "input stream ready" callback.
+ bool mWaitingForInputStreamReady;
+ bool mCloseWhenDone;
+ bool mRetargeting;
+ // Protects state/member var accesses across multiple threads.
+ mozilla::ReentrantMonitor mMonitor;
+};
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp
new file mode 100644
index 000000000..3b8cf4434
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsLoadGroup.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Telemetry.h"
+#include "nsITimedChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRequestObserver.h"
+#include "nsIRequestContext.h"
+#include "CacheObserver.h"
+#include "MainThreadUtils.h"
+
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// Log module for nsILoadGroup logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=LoadGroup:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+//
+static LazyLogModule gLoadGroupLog("LoadGroup");
+#undef LOG
+#define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class RequestMapEntry : public PLDHashEntryHdr
+{
+public:
+ explicit RequestMapEntry(nsIRequest *aRequest) :
+ mKey(aRequest)
+ {
+ }
+
+ nsCOMPtr<nsIRequest> mKey;
+};
+
+static bool
+RequestHashMatchEntry(const PLDHashEntryHdr *entry, const void *key)
+{
+ const RequestMapEntry *e =
+ static_cast<const RequestMapEntry *>(entry);
+ const nsIRequest *request = static_cast<const nsIRequest *>(key);
+
+ return e->mKey == request;
+}
+
+static void
+RequestHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
+{
+ RequestMapEntry *e = static_cast<RequestMapEntry *>(entry);
+
+ // An entry is being cleared, let the entry do its own cleanup.
+ e->~RequestMapEntry();
+}
+
+static void
+RequestHashInitEntry(PLDHashEntryHdr *entry, const void *key)
+{
+ const nsIRequest *const_request = static_cast<const nsIRequest *>(key);
+ nsIRequest *request = const_cast<nsIRequest *>(const_request);
+
+ // Initialize the entry with placement new
+ new (entry) RequestMapEntry(request);
+}
+
+static const PLDHashTableOps sRequestHashOps =
+{
+ PLDHashTable::HashVoidPtrKeyStub,
+ RequestHashMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ RequestHashClearEntry,
+ RequestHashInitEntry
+};
+
+static void
+RescheduleRequest(nsIRequest *aRequest, int32_t delta)
+{
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest);
+ if (p)
+ p->AdjustPriority(delta);
+}
+
+nsLoadGroup::nsLoadGroup(nsISupports* outer)
+ : mForegroundCount(0)
+ , mLoadFlags(LOAD_NORMAL)
+ , mDefaultLoadFlags(0)
+ , mRequests(&sRequestHashOps, sizeof(RequestMapEntry))
+ , mStatus(NS_OK)
+ , mPriority(PRIORITY_NORMAL)
+ , mIsCanceling(false)
+ , mDefaultLoadIsTimed(false)
+ , mTimedRequests(0)
+ , mCachedRequests(0)
+ , mTimedNonCachedRequestsUntilOnEndPageLoad(0)
+{
+ NS_INIT_AGGREGATED(outer);
+ LOG(("LOADGROUP [%x]: Created.\n", this));
+}
+
+nsLoadGroup::~nsLoadGroup()
+{
+ DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
+
+ mDefaultLoadRequest = nullptr;
+
+ if (mRequestContext) {
+ nsID rcid;
+ mRequestContext->GetID(&rcid);
+
+ if (IsNeckoChild() && gNeckoChild) {
+ char rcid_str[NSID_LENGTH];
+ rcid.ToProvidedString(rcid_str);
+
+ nsCString rcid_nscs;
+ rcid_nscs.AssignASCII(rcid_str);
+
+ gNeckoChild->SendRemoveRequestContext(rcid_nscs);
+ } else {
+ mRequestContextService->RemoveRequestContext(rcid);
+ }
+ }
+
+ LOG(("LOADGROUP [%x]: Destroyed.\n", this));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports methods:
+
+NS_IMPL_AGGREGATED(nsLoadGroup)
+NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup)
+ NS_INTERFACE_MAP_ENTRY(nsILoadGroup)
+ NS_INTERFACE_MAP_ENTRY(nsPILoadGroupInternal)
+ NS_INTERFACE_MAP_ENTRY(nsILoadGroupChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetName(nsACString &result)
+{
+ // XXX is this the right "name" for a load group?
+
+ if (!mDefaultLoadRequest) {
+ result.Truncate();
+ return NS_OK;
+ }
+
+ return mDefaultLoadRequest->GetName(result);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::IsPending(bool *aResult)
+{
+ *aResult = (mForegroundCount > 0) ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetStatus(nsresult *status)
+{
+ if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest)
+ return mDefaultLoadRequest->GetStatus(status);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+static bool
+AppendRequestsToArray(PLDHashTable* aTable, nsTArray<nsIRequest*> *aArray)
+{
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ nsIRequest *request = e->mKey;
+ NS_ASSERTION(request, "What? Null key in PLDHashTable entry?");
+
+ bool ok = !!aArray->AppendElement(request);
+ if (!ok) {
+ break;
+ }
+ NS_ADDREF(request);
+ }
+
+ if (aArray->Length() != aTable->EntryCount()) {
+ for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) {
+ NS_RELEASE((*aArray)[i]);
+ }
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Cancel(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
+ nsresult rv;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // set the load group status to our cancel status while we cancel
+ // all our requests...once the cancel is done, we'll reset it...
+ //
+ mStatus = status;
+
+ // Set the flag indicating that the loadgroup is being canceled... This
+ // prevents any new channels from being added during the operation.
+ //
+ mIsCanceling = true;
+
+ nsresult firstError = NS_OK;
+
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+
+ if (!mRequests.Search(request)) {
+ // |request| was removed already
+ NS_RELEASE(request);
+ continue;
+ }
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Canceling request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ //
+ // Remove the request from the load group... This may cause
+ // the OnStopRequest notification to fire...
+ //
+ // XXX: What should the context be?
+ //
+ (void)RemoveRequest(request, nullptr, status);
+
+ // Cancel the request...
+ rv = request->Cancel(status);
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+#if defined(DEBUG)
+ NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
+ NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
+#endif
+
+ mStatus = NS_OK;
+ mIsCanceling = false;
+
+ return firstError;
+}
+
+
+NS_IMETHODIMP
+nsLoadGroup::Suspend()
+{
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request)
+ continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Suspending request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ // Suspend the request...
+ rv = request->Suspend();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+ return firstError;
+}
+
+
+NS_IMETHODIMP
+nsLoadGroup::Resume()
+{
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsIRequest* request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request)
+ continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Resuming request %x %s.\n",
+ this, request, nameStr.get()));
+ }
+
+ // Resume the request...
+ rv = request->Resume();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError))
+ firstError = rv;
+
+ NS_RELEASE(request);
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadFlags(uint32_t *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadGroup(nsILoadGroup **loadGroup)
+{
+ *loadGroup = mLoadGroup;
+ NS_IF_ADDREF(*loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadGroup(nsILoadGroup *loadGroup)
+{
+ mLoadGroup = loadGroup;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroup methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadRequest(nsIRequest * *aRequest)
+{
+ *aRequest = mDefaultLoadRequest;
+ NS_IF_ADDREF(*aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadRequest(nsIRequest *aRequest)
+{
+ mDefaultLoadRequest = aRequest;
+ // Inherit the group load flags from the default load request
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetLoadFlags(&mLoadFlags);
+ //
+ // Mask off any bits that are not part of the nsIRequest flags.
+ // in particular, nsIChannel::LOAD_DOCUMENT_URI...
+ //
+ mLoadFlags &= nsIRequest::LOAD_REQUESTMASK;
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest);
+ mDefaultLoadIsTimed = timedChannel != nullptr;
+ if (mDefaultLoadIsTimed) {
+ timedChannel->GetChannelCreation(&mDefaultRequestCreationTime);
+ timedChannel->SetTimingEnabled(true);
+ }
+ }
+ // Else, do not change the group's load flags (see bug 95981)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AddRequest(nsIRequest *request, nsISupports* ctxt)
+{
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Adding request %x %s (count=%d).\n",
+ this, request, nameStr.get(), mRequests.EntryCount()));
+ }
+
+ NS_ASSERTION(!mRequests.Search(request),
+ "Entry added to loadgroup twice, don't do that");
+
+ //
+ // Do not add the channel, if the loadgroup is being canceled...
+ //
+ if (mIsCanceling) {
+ LOG(("LOADGROUP [%x]: AddChannel() ABORTED because LoadGroup is"
+ " being canceled!!\n", this));
+
+ return NS_BINDING_ABORTED;
+ }
+
+ nsLoadFlags flags;
+ // if the request is the default load request or if the default load
+ // request is null, then the load group should inherit its load flags from
+ // the request, but also we need to enforce defaultLoadFlags.
+ if (mDefaultLoadRequest == request || !mDefaultLoadRequest) {
+ rv = MergeDefaultLoadFlags(request, flags);
+ } else {
+ rv = MergeLoadFlags(request, flags);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Add the request to the list of active requests...
+ //
+
+ auto entry =
+ static_cast<RequestMapEntry*>(mRequests.Add(request, fallible));
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mPriority != 0)
+ RescheduleRequest(request, mPriority);
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel)
+ timedChannel->SetTimingEnabled(true);
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ // Update the count of foreground URIs..
+ mForegroundCount += 1;
+
+ //
+ // Fire the OnStartRequest notification out to the observer...
+ //
+ // If the notification fails then DO NOT add the request to
+ // the load group.
+ //
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(("LOADGROUP [%x]: Firing OnStartRequest for request %x."
+ "(foreground count=%d).\n", this, request, mForegroundCount));
+
+ rv = observer->OnStartRequest(request, ctxt);
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%x]: OnStartRequest for request %x FAILED.\n",
+ this, request));
+ //
+ // The URI load has been canceled by the observer. Clean up
+ // the damage...
+ //
+
+ mRequests.Remove(request);
+
+ rv = NS_OK;
+
+ mForegroundCount -= 1;
+ }
+ }
+
+ // Ensure that we're part of our loadgroup while pending
+ if (mForegroundCount == 1 && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::RemoveRequest(nsIRequest *request, nsISupports* ctxt,
+ nsresult aStatus)
+{
+ NS_ENSURE_ARG_POINTER(request);
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%x]: Removing request %x %s status %x (count=%d).\n",
+ this, request, nameStr.get(), aStatus, mRequests.EntryCount() - 1));
+ }
+
+ // Make sure we have a owning reference to the request we're about
+ // to remove.
+
+ nsCOMPtr<nsIRequest> kungFuDeathGrip(request);
+
+ //
+ // Remove the request from the group. If this fails, it means that
+ // the request was *not* in the group so do not update the foreground
+ // count or it will get messed up...
+ //
+ auto entry = static_cast<RequestMapEntry*>(mRequests.Search(request));
+
+ if (!entry) {
+ LOG(("LOADGROUP [%x]: Unable to remove request %x. Not in group!\n",
+ this, request));
+
+ return NS_ERROR_FAILURE;
+ }
+
+ mRequests.RemoveEntry(entry);
+
+ // Collect telemetry stats only when default request is a timed channel.
+ // Don't include failed requests in the timing statistics.
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) {
+ // Figure out if this request was served from the cache
+ ++mTimedRequests;
+ TimeStamp timeStamp;
+ rv = timedChannel->GetCacheReadStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ ++mCachedRequests;
+ }
+ else {
+ mTimedNonCachedRequestsUntilOnEndPageLoad++;
+ }
+
+ rv = timedChannel->GetAsyncOpen(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ rv = timedChannel->GetResponseStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ TelemetryReportChannel(timedChannel, false);
+ }
+ }
+
+ if (mRequests.EntryCount() == 0) {
+ TelemetryReport();
+ }
+
+ // Undo any group priority delta...
+ if (mPriority != 0)
+ RescheduleRequest(request, -mPriority);
+
+ nsLoadFlags flags;
+ rv = request->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
+ mForegroundCount -= 1;
+
+ // Fire the OnStopRequest out to the observer...
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(("LOADGROUP [%x]: Firing OnStopRequest for request %x."
+ "(foreground count=%d).\n", this, request, mForegroundCount));
+
+ rv = observer->OnStopRequest(request, ctxt, aStatus);
+
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%x]: OnStopRequest for request %x FAILED.\n",
+ this, request));
+ }
+ }
+
+ // If that was the last request -> remove ourselves from loadgroup
+ if (mForegroundCount == 0 && mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequests(nsISimpleEnumerator * *aRequests)
+{
+ nsCOMArray<nsIRequest> requests;
+ requests.SetCapacity(mRequests.EntryCount());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ requests.AppendObject(e->mKey);
+ }
+
+ return NS_NewArrayEnumerator(aRequests, requests);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver)
+{
+ mObserver = do_GetWeakReference(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetGroupObserver(nsIRequestObserver* *aResult)
+{
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ *aResult = observer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetActiveCount(uint32_t* aResult)
+{
+ *aResult = mForegroundCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequestContextID(nsID *aRCID)
+{
+ if (!mRequestContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mRequestContext->GetID(aRCID);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroupChild methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetParentLoadGroup(nsILoadGroup * *aParentLoadGroup)
+{
+ *aParentLoadGroup = nullptr;
+ nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup);
+ if (!parent)
+ return NS_OK;
+ parent.forget(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetParentLoadGroup(nsILoadGroup *aParentLoadGroup)
+{
+ mParentLoadGroup = do_GetWeakReference(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetChildLoadGroup(nsILoadGroup * *aChildLoadGroup)
+{
+ NS_ADDREF(*aChildLoadGroup = this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRootLoadGroup(nsILoadGroup * *aRootLoadGroup)
+{
+ // first recursively try the root load group of our parent
+ nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup);
+ if (ancestor)
+ return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // next recursively try the root load group of our own load grop
+ ancestor = do_QueryInterface(mLoadGroup);
+ if (ancestor)
+ return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // finally just return this
+ NS_ADDREF(*aRootLoadGroup = this);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPILoadGroupInternal methods:
+
+NS_IMETHODIMP
+nsLoadGroup::OnEndPageLoad(nsIChannel *aDefaultChannel)
+{
+ // for the moment, nothing to do here.
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupportsPriority methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetPriority(int32_t *aValue)
+{
+ *aValue = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetPriority(int32_t aValue)
+{
+ return AdjustPriority(aValue - mPriority);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AdjustPriority(int32_t aDelta)
+{
+ // Update the priority for each request that supports nsISupportsPriority
+ if (aDelta != 0) {
+ mPriority += aDelta;
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ RescheduleRequest(e->mKey, aDelta);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadFlags(uint32_t *aFlags)
+{
+ *aFlags = mDefaultLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags)
+{
+ mDefaultLoadFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetUserAgentOverrideCache(nsACString & aUserAgentOverrideCache)
+{
+ aUserAgentOverrideCache = mUserAgentOverrideCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetUserAgentOverrideCache(const nsACString & aUserAgentOverrideCache)
+{
+ mUserAgentOverrideCache = aUserAgentOverrideCache;
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsLoadGroup::TelemetryReport()
+{
+ if (mDefaultLoadIsTimed) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests);
+ if (mTimedRequests) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE,
+ mCachedRequests * 100 / mTimedRequests);
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel =
+ do_QueryInterface(mDefaultLoadRequest);
+ if (timedChannel)
+ TelemetryReportChannel(timedChannel, true);
+ }
+
+ mTimedRequests = 0;
+ mCachedRequests = 0;
+ mDefaultLoadIsTimed = false;
+}
+
+void
+nsLoadGroup::TelemetryReportChannel(nsITimedChannel *aTimedChannel,
+ bool aDefaultRequest)
+{
+ nsresult rv;
+ bool timingEnabled;
+ rv = aTimedChannel->GetTimingEnabled(&timingEnabled);
+ if (NS_FAILED(rv) || !timingEnabled)
+ return;
+
+ TimeStamp asyncOpen;
+ rv = aTimedChannel->GetAsyncOpen(&asyncOpen);
+ // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way
+ if (NS_FAILED(rv) || asyncOpen.IsNull())
+ return;
+
+ TimeStamp cacheReadStart;
+ rv = aTimedChannel->GetCacheReadStart(&cacheReadStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp cacheReadEnd;
+ rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp domainLookupStart;
+ rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp domainLookupEnd;
+ rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp connectStart;
+ rv = aTimedChannel->GetConnectStart(&connectStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp connectEnd;
+ rv = aTimedChannel->GetConnectEnd(&connectEnd);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp requestStart;
+ rv = aTimedChannel->GetRequestStart(&requestStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp responseStart;
+ rv = aTimedChannel->GetResponseStart(&responseStart);
+ if (NS_FAILED(rv))
+ return;
+
+ TimeStamp responseEnd;
+ rv = aTimedChannel->GetResponseEnd(&responseEnd);
+ if (NS_FAILED(rv))
+ return;
+
+#define HTTP_REQUEST_HISTOGRAMS(prefix) \
+ if (!domainLookupStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \
+ asyncOpen, domainLookupStart); \
+ } \
+ \
+ if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \
+ domainLookupStart, domainLookupEnd); \
+ } \
+ \
+ if (!connectStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_TCP_CONNECTION, \
+ connectStart, connectEnd); \
+ } \
+ \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, \
+ asyncOpen, requestStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, \
+ requestStart, responseEnd); \
+ \
+ if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, \
+ asyncOpen, responseStart); \
+ } \
+ } \
+ \
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE, \
+ asyncOpen, cacheReadStart); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, \
+ asyncOpen, cacheReadStart); \
+ } \
+ \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME, \
+ cacheReadStart, cacheReadEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, \
+ cacheReadStart, cacheReadEnd); \
+ } \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_REVALIDATION, \
+ requestStart, responseEnd); \
+ } \
+ } \
+ \
+ if (!cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \
+ asyncOpen, cacheReadEnd); \
+ \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED, \
+ asyncOpen, cacheReadEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, \
+ asyncOpen, cacheReadEnd); \
+ } \
+ } \
+ else if (!responseEnd.IsNull()) { \
+ if (!CacheObserver::UseNewCache()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \
+ asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET, \
+ asyncOpen, responseEnd); \
+ } else { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, \
+ asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, \
+ asyncOpen, responseEnd); \
+ } \
+ }
+
+ if (aDefaultRequest) {
+ HTTP_REQUEST_HISTOGRAMS(PAGE)
+ } else {
+ HTTP_REQUEST_HISTOGRAMS(SUB)
+ }
+#undef HTTP_REQUEST_HISTOGRAMS
+}
+
+nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest,
+ nsLoadFlags& outFlags)
+{
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+
+ // Inherit the following bits...
+ flags |= (mLoadFlags & (LOAD_BACKGROUND |
+ LOAD_BYPASS_CACHE |
+ LOAD_FROM_CACHE |
+ VALIDATE_ALWAYS |
+ VALIDATE_ONCE_PER_SESSION |
+ VALIDATE_NEVER));
+
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest *aRequest,
+ nsLoadFlags& outFlags)
+{
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::Init()
+{
+ mRequestContextService = do_GetService("@mozilla.org/network/request-context-service;1");
+ if (mRequestContextService) {
+ nsID requestContextID;
+ if (NS_SUCCEEDED(mRequestContextService->NewRequestContextID(&requestContextID))) {
+ mRequestContextService->GetRequestContext(requestContextID,
+ getter_AddRefs(mRequestContext));
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h
new file mode 100644
index 000000000..da89ca1b3
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadGroup_h__
+#define nsLoadGroup_h__
+
+#include "nsILoadGroup.h"
+#include "nsILoadGroupChild.h"
+#include "nsPILoadGroupInternal.h"
+#include "nsAgg.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+#include "nsWeakReference.h"
+#include "nsISupportsPriority.h"
+#include "PLDHashTable.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIRequestContext;
+class nsIRequestContextService;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace net {
+
+class nsLoadGroup : public nsILoadGroup,
+ public nsILoadGroupChild,
+ public nsISupportsPriority,
+ public nsSupportsWeakReference,
+ public nsPILoadGroupInternal
+{
+public:
+ NS_DECL_AGGREGATED
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIRequest methods:
+ NS_DECL_NSIREQUEST
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroup methods:
+ NS_DECL_NSILOADGROUP
+ NS_DECL_NSPILOADGROUPINTERNAL
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroupChild methods:
+ NS_DECL_NSILOADGROUPCHILD
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsISupportsPriority methods:
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsLoadGroup methods:
+
+ explicit nsLoadGroup(nsISupports* outer);
+ virtual ~nsLoadGroup();
+
+ nsresult Init();
+
+protected:
+ nsresult MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags);
+ nsresult MergeDefaultLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags);
+
+private:
+ void TelemetryReport();
+ void TelemetryReportChannel(nsITimedChannel *timedChannel,
+ bool defaultRequest);
+
+protected:
+ uint32_t mForegroundCount;
+ uint32_t mLoadFlags;
+ uint32_t mDefaultLoadFlags;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ nsCOMPtr<nsIRequest> mDefaultLoadRequest;
+ PLDHashTable mRequests;
+
+ nsWeakPtr mObserver;
+ nsWeakPtr mParentLoadGroup;
+
+ nsresult mStatus;
+ int32_t mPriority;
+ bool mIsCanceling;
+
+ /* Telemetry */
+ mozilla::TimeStamp mDefaultRequestCreationTime;
+ bool mDefaultLoadIsTimed;
+ uint32_t mTimedRequests;
+ uint32_t mCachedRequests;
+
+ /* For nsPILoadGroupInternal */
+ uint32_t mTimedNonCachedRequestsUntilOnEndPageLoad;
+
+ nsCString mUserAgentOverrideCache;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsLoadGroup_h__
diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp
new file mode 100644
index 000000000..ce1188ea0
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMIMEInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStringStream.h"
+#include "nsString.h"
+#include "nsMIMEInputStream.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+
+class nsMIMEInputStream : public nsIMIMEInputStream,
+ public nsISeekableStream,
+ public nsIIPCSerializableInputStream
+{
+ virtual ~nsMIMEInputStream();
+
+public:
+ nsMIMEInputStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIMIMEINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ nsresult Init();
+
+private:
+
+ void InitStreams();
+
+ struct MOZ_STACK_CLASS ReadSegmentsState {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ nsWriteSegmentFun mWriter;
+ void* mClosure;
+ };
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t *aWriteCount);
+
+ nsCString mHeaders;
+ nsCOMPtr<nsIStringInputStream> mHeaderStream;
+
+ nsCString mContentLength;
+ nsCOMPtr<nsIStringInputStream> mCLStream;
+
+ nsCOMPtr<nsIInputStream> mData;
+ nsCOMPtr<nsIMultiplexInputStream> mStream;
+ bool mAddContentLength;
+ bool mStartedReading;
+};
+
+NS_IMPL_ADDREF(nsMIMEInputStream)
+NS_IMPL_RELEASE(nsMIMEInputStream)
+
+NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_MIMEINPUTSTREAM_CID)
+
+NS_IMPL_QUERY_INTERFACE_CI(nsMIMEInputStream,
+ nsIMIMEInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream)
+NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
+ nsIMIMEInputStream,
+ nsIInputStream,
+ nsISeekableStream)
+
+nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false),
+ mStartedReading(false)
+{
+}
+
+nsMIMEInputStream::~nsMIMEInputStream()
+{
+}
+
+nsresult nsMIMEInputStream::Init()
+{
+ nsresult rv = NS_OK;
+ mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStream->AppendStream(mHeaderStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStream->AppendStream(mCLStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength)
+{
+ *aAddContentLength = mAddContentLength;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsMIMEInputStream::SetAddContentLength(bool aAddContentLength)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ mAddContentLength = aAddContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AddHeader(const char *aName, const char *aValue)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ mHeaders.Append(aName);
+ mHeaders.AppendLiteral(": ");
+ mHeaders.Append(aValue);
+ mHeaders.AppendLiteral("\r\n");
+
+ // Just in case someone somehow uses our stream, lets at least
+ // let the stream have a valid pointer. The stream will be properly
+ // initialized in nsMIMEInputStream::InitStreams
+ mHeaderStream->ShareData(mHeaders.get(), 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::SetData(nsIInputStream *aStream)
+{
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+ // Remove the old stream if there is one
+ if (mData)
+ mStream->RemoveStream(2);
+
+ mData = aStream;
+ if (aStream)
+ mStream->AppendStream(mData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetData(nsIInputStream **aStream)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = mData;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+// set up the internal streams
+void nsMIMEInputStream::InitStreams()
+{
+ NS_ASSERTION(!mStartedReading,
+ "Don't call initStreams twice without rewinding");
+
+ mStartedReading = true;
+
+ // We'll use the content-length stream to add the final \r\n
+ if (mAddContentLength) {
+ uint64_t cl = 0;
+ if (mData) {
+ mData->Available(&cl);
+ }
+ mContentLength.AssignLiteral("Content-Length: ");
+ mContentLength.AppendInt(cl);
+ mContentLength.AppendLiteral("\r\n\r\n");
+ }
+ else {
+ mContentLength.AssignLiteral("\r\n");
+ }
+ mCLStream->ShareData(mContentLength.get(), -1);
+ mHeaderStream->ShareData(mHeaders.get(), -1);
+}
+
+
+
+#define INITSTREAMS \
+if (!mStartedReading) { \
+ InitStreams(); \
+}
+
+// Reset mStartedReading when Seek-ing to start
+NS_IMETHODIMP
+nsMIMEInputStream::Seek(int32_t whence, int64_t offset)
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ if (whence == NS_SEEK_SET && offset == 0) {
+ rv = stream->Seek(whence, offset);
+ if (NS_SUCCEEDED(rv))
+ mStartedReading = false;
+ }
+ else {
+ INITSTREAMS;
+ rv = stream->Seek(whence, offset);
+ }
+
+ return rv;
+}
+
+// Proxy ReadSegments since we need to be a good little nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void *aClosure, uint32_t aCount,
+ uint32_t *_retval)
+{
+ INITSTREAMS;
+ ReadSegmentsState state;
+ state.mThisStream = this;
+ state.mWriter = aWriter;
+ state.mClosure = aClosure;
+ return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval);
+}
+
+nsresult
+nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+ return (state->mWriter)(state->mThisStream,
+ state->mClosure,
+ aFromRawSegment,
+ aToOffset,
+ aCount,
+ aWriteCount);
+}
+
+/**
+ * Forward everything else to the mStream after calling InitStreams()
+ */
+
+// nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::Close(void) { INITSTREAMS; return mStream->Close(); }
+NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t *_retval) { INITSTREAMS; return mStream->Available(_retval); }
+NS_IMETHODIMP nsMIMEInputStream::Read(char * buf, uint32_t count, uint32_t *_retval) { INITSTREAMS; return mStream->Read(buf, count, _retval); }
+NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool *aNonBlocking) { INITSTREAMS; return mStream->IsNonBlocking(aNonBlocking); }
+
+// nsISeekableStream
+NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t *_retval)
+{
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->Tell(_retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->SetEOF();
+}
+
+
+/**
+ * Factory method used by do_CreateInstance
+ */
+
+nsresult
+nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result)
+{
+ *result = nullptr;
+
+ if (outer)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsMIMEInputStream *inst = new nsMIMEInputStream();
+ if (!inst)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(inst);
+
+ nsresult rv = inst->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(inst);
+ return rv;
+ }
+
+ rv = inst->QueryInterface(iid, result);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+void
+nsMIMEInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ MIMEInputStreamParams params;
+
+ if (mData) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mData);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ SerializeInputStream(stream, wrappedParams, aFileDescriptors);
+
+ NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
+ "Wrapped stream failed to serialize!");
+
+ params.optionalStream() = wrappedParams;
+ }
+ else {
+ params.optionalStream() = mozilla::void_t();
+ }
+
+ params.headers() = mHeaders;
+ params.contentLength() = mContentLength;
+ params.startedReading() = mStartedReading;
+ params.addContentLength() = mAddContentLength;
+
+ aParams = params;
+}
+
+bool
+nsMIMEInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MIMEInputStreamParams& params =
+ aParams.get_MIMEInputStreamParams();
+ const OptionalInputStreamParams& wrappedParams = params.optionalStream();
+
+ mHeaders = params.headers();
+ mContentLength = params.contentLength();
+ mStartedReading = params.startedReading();
+
+ // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream
+ mHeaderStream->ShareData(mHeaders.get(),
+ mStartedReading ? mHeaders.Length() : 0);
+ mCLStream->ShareData(mContentLength.get(),
+ mStartedReading ? mContentLength.Length() : 0);
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
+ stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
+ aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+
+ mData = stream;
+
+ if (NS_FAILED(mStream->AppendStream(mData))) {
+ NS_WARNING("Failed to append stream!");
+ return false;
+ }
+ }
+ else {
+ NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
+ "Unknown type for OptionalInputStreamParams!");
+ }
+
+ mAddContentLength = params.addContentLength();
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsMIMEInputStream::ExpectedSerializedLength()
+{
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
+ return serializable ? serializable->ExpectedSerializedLength() : Nothing();
+}
+
diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h
new file mode 100644
index 000000000..38823d7ae
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#ifndef _nsMIMEInputStream_h_
+#define _nsMIMEInputStream_h_
+
+#include "nsIMIMEInputStream.h"
+
+#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1"
+#define NS_MIMEINPUTSTREAM_CID \
+{ /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \
+ 0x58a1c31c, \
+ 0x1dd2, \
+ 0x11b2, \
+ {0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68} \
+}
+
+extern nsresult nsMIMEInputStreamConstructor(nsISupports *outer,
+ REFNSIID iid,
+ void **result);
+
+#endif // _nsMIMEInputStream_h_
diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp
new file mode 100644
index 000000000..af73fe52c
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsEscape.h"
+#include "nsIURI.h"
+#include <utility>
+
+#include "nsMediaFragmentURIParser.h"
+
+using std::pair;
+using std::make_pair;
+
+namespace mozilla { namespace net {
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI)
+ : mClipUnit(eClipUnit_Pixel)
+{
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ Parse(ref);
+}
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef)
+ : mClipUnit(eClipUnit_Pixel)
+{
+ Parse(aRef);
+}
+
+bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString)
+{
+ nsDependentSubstring original(aString);
+ if (aString.Length() > 4 &&
+ aString[0] == 'n' && aString[1] == 'p' &&
+ aString[2] == 't' && aString[3] == ':') {
+ aString.Rebind(aString, 4);
+ }
+
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ double start = -1.0;
+ double end = -1.0;
+
+ ParseNPTTime(aString, start);
+
+ if (aString.Length() == 0) {
+ mStart.emplace(start);
+ return true;
+ }
+
+ if (aString[0] != ',') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+
+ if (aString.Length() == 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ ParseNPTTime(aString, end);
+
+ if (end <= start || aString.Length() != 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ mStart.emplace(start);
+ mEnd.emplace(end);
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString, double& aTime)
+{
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ return
+ ParseNPTHHMMSS(aString, aTime) ||
+ ParseNPTMMSS(aString, aTime) ||
+ ParseNPTSec(aString, aTime);
+}
+
+// Return true if the given character is a numeric character
+static bool IsDigit(nsDependentSubstring::char_type aChar)
+{
+ return (aChar >= '0' && aChar <= '9');
+}
+
+// Return the index of the first character in the string that is not
+// a numerical digit, starting from 'aStart'.
+static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart)
+{
+ while (aStart < aString.Length() && IsDigit(aString[aStart])) {
+ ++aStart;
+ }
+ return aStart;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString, double& aSec)
+{
+ nsDependentSubstring original(aString);
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ double fraction = 0.0;
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aSec = s + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString, double& aTime)
+{
+ nsDependentSubstring original(aString);
+ uint32_t mm = 0;
+ uint32_t ss = 0;
+ double fraction = 0.0;
+ if (!ParseNPTMM(aString, mm)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTSS(aString, ss)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+ aTime = mm * 60 + ss + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString, double& aFraction)
+{
+ double fraction = 0.0;
+
+ if (aString.Length() > 0 && aString[0] == '.') {
+ uint32_t index = FirstNonDigit(aString, 1);
+
+ if (index > 1) {
+ nsDependentSubstring number(aString, 0, index);
+ nsresult ec;
+ fraction = PromiseFlatString(number).ToDouble(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+ }
+ aString.Rebind(aString, index);
+ }
+
+ aFraction = fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime)
+{
+ nsDependentSubstring original(aString);
+ uint32_t hh = 0;
+ double seconds = 0.0;
+ if (!ParseNPTHH(aString, hh)) {
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTMMSS(aString, seconds)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aTime = hh * 3600 + seconds;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour)
+{
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t u = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aHour = u;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute)
+{
+ return ParseNPTSS(aString, aMinute);
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond)
+{
+ if (aString.Length() < 2) {
+ return false;
+ }
+
+ if (IsDigit(aString[0]) && IsDigit(aString[1])) {
+ nsDependentSubstring n(aString, 0, 2);
+ nsresult ec;
+ int32_t u = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, 2);
+ if (u >= 60)
+ return false;
+
+ aSecond = u;
+ return true;
+ }
+
+ return false;
+}
+
+static bool ParseInteger(nsDependentSubstring& aString,
+ int32_t& aResult)
+{
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = PromiseFlatString(n).ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aResult = s;
+ return true;
+}
+
+static bool ParseCommaSeparator(nsDependentSubstring& aString)
+{
+ if (aString.Length() > 1 && aString[0] == ',') {
+ aString.Rebind(aString, 1);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString)
+{
+ int32_t x, y, w, h;
+ ClipUnit clipUnit;
+
+ // Determine units.
+ if (StringBeginsWith(aString, NS_LITERAL_STRING("pixel:"))) {
+ clipUnit = eClipUnit_Pixel;
+ aString.Rebind(aString, 6);
+ } else if (StringBeginsWith(aString, NS_LITERAL_STRING("percent:"))) {
+ clipUnit = eClipUnit_Percent;
+ aString.Rebind(aString, 8);
+ } else {
+ clipUnit = eClipUnit_Pixel;
+ }
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, x) && x >= 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, y) && y >= 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, w) && w > 0 &&
+ ParseCommaSeparator(aString) &&
+ ParseInteger(aString, h) && h > 0 &&
+ aString.Length() == 0) {
+
+ // Reject invalid percentage coordinates.
+ if (clipUnit == eClipUnit_Percent &&
+ (x + w > 100 || y + h > 100)) {
+ return false;
+ }
+
+ mClip.emplace(x, y, w, h);
+ mClipUnit = clipUnit;
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseMozSampleSize(nsDependentSubstring aString)
+{
+ int32_t sampleSize;
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, sampleSize) && sampleSize > 0) {
+ mSampleSize.emplace(sampleSize);
+ return true;
+ }
+
+ return false;
+}
+
+void nsMediaFragmentURIParser::Parse(nsACString& aRef)
+{
+ // Create an array of possibly-invalid media fragments.
+ nsTArray< std::pair<nsCString, nsCString> > fragments;
+ nsCCharSeparatedTokenizer tokenizer(aRef, '&');
+
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& nv = tokenizer.nextToken();
+ int32_t index = nv.FindChar('=');
+ if (index >= 0) {
+ nsAutoCString name;
+ nsAutoCString value;
+ NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
+ NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
+ esc_Ref | esc_AlwaysCopy, value);
+ fragments.AppendElement(make_pair(name, value));
+ }
+ }
+
+ // Parse the media fragment values.
+ bool gotTemporal = false, gotSpatial = false, gotSampleSize = false;
+ for (int i = fragments.Length() - 1 ; i >= 0 ; --i) {
+ if (gotTemporal && gotSpatial && gotSampleSize) {
+ // We've got one of each possible type. No need to look at the rest.
+ break;
+ } else if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
+ } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
+ } else if (!gotSampleSize && fragments[i].first.EqualsLiteral("-moz-samplesize")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSampleSize = ParseMozSampleSize(nsDependentSubstring(value, 0));
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h
new file mode 100644
index 000000000..acfa1d5fe
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(nsMediaFragmentURIParser_h__)
+#define nsMediaFragmentURIParser_h__
+
+#include "mozilla/Maybe.h"
+#include "nsStringFwd.h"
+#include "nsRect.h"
+
+class nsIURI;
+
+// Class to handle parsing of a W3C media fragment URI as per
+// spec at: http://www.w3.org/TR/media-frags/
+// Only the temporaral URI portion of the spec is implemented.
+// To use:
+// a) Construct an instance with the URI containing the fragment
+// b) Check for the validity of the values you are interested in
+// using e.g. HasStartTime().
+// c) If the values are valid, obtain them using e.g. GetStartTime().
+
+namespace mozilla { namespace net {
+
+enum ClipUnit
+{
+ eClipUnit_Pixel,
+ eClipUnit_Percent,
+};
+
+class nsMediaFragmentURIParser
+{
+public:
+ // Create a parser with the provided URI.
+ explicit nsMediaFragmentURIParser(nsIURI* aURI);
+
+ // Create a parser with the provided URI reference portion.
+ explicit nsMediaFragmentURIParser(nsCString& aRef);
+
+ // True if a valid temporal media fragment indicated a start time.
+ bool HasStartTime() const { return mStart.isSome(); }
+
+ // If a valid temporal media fragment indicated a start time, returns
+ // it in units of seconds. If not, defaults to 0.
+ double GetStartTime() const { return *mStart; }
+
+ // True if a valid temporal media fragment indicated an end time.
+ bool HasEndTime() const { return mEnd.isSome(); }
+
+ // If a valid temporal media fragment indicated an end time, returns
+ // it in units of seconds. If not, defaults to -1.
+ double GetEndTime() const { return *mEnd; }
+
+ // True if a valid spatial media fragment indicated a clipping region.
+ bool HasClip() const { return mClip.isSome(); }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the region. If not, returns an empty region. The unit
+ // used depends on the value returned by GetClipUnit().
+ nsIntRect GetClip() const { return *mClip; }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the unit used.
+ ClipUnit GetClipUnit() const { return mClipUnit; }
+
+ bool HasSampleSize() const { return mSampleSize.isSome(); }
+
+ int GetSampleSize() const { return *mSampleSize; }
+
+private:
+ // Parse the URI ref provided, looking for media fragments. This is
+ // the top-level parser the invokes the others below.
+ void Parse(nsACString& aRef);
+
+ // The following methods parse the fragment as per the media
+ // fragments specification. 'aString' contains the remaining
+ // fragment data to be parsed. The method returns true
+ // if the parse was successful and leaves the remaining unparsed
+ // data in 'aString'. If the parse fails then false is returned
+ // and 'aString' is left as it was when called.
+ bool ParseNPT(nsDependentSubstring aString);
+ bool ParseNPTTime(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTSec(nsDependentSubstring& aString, double& aSec);
+ bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
+ bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour);
+ bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute);
+ bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond);
+ bool ParseXYWH(nsDependentSubstring aString);
+ bool ParseMozResolution(nsDependentSubstring aString);
+ bool ParseMozSampleSize(nsDependentSubstring aString);
+
+ // Media fragment information.
+ Maybe<double> mStart;
+ Maybe<double> mEnd;
+ Maybe<nsIntRect> mClip;
+ ClipUnit mClipUnit;
+ Maybe<int> mSampleSize;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp
new file mode 100644
index 000000000..8d6f245cf
--- /dev/null
+++ b/netwerk/base/nsNetAddr.cpp
@@ -0,0 +1,152 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNetAddr.h"
+#include "nsString.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsNetAddr, nsINetAddr)
+
+/* Makes a copy of |addr| */
+nsNetAddr::nsNetAddr(NetAddr* addr)
+{
+ NS_ASSERTION(addr, "null addr");
+ mAddr = *addr;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFamily(uint16_t *aFamily)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET:
+ *aFamily = nsINetAddr::FAMILY_INET;
+ break;
+ case AF_INET6:
+ *aFamily = nsINetAddr::FAMILY_INET6;
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ *aFamily = nsINetAddr::FAMILY_LOCAL;
+ break;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetAddress(nsACString & aAddress)
+{
+ switch(mAddr.raw.family) {
+ /* PR_NetAddrToString can handle INET and INET6, but not LOCAL. */
+ case AF_INET:
+ aAddress.SetCapacity(kIPv4CStrBufSize);
+ NetAddrToString(&mAddr, aAddress.BeginWriting(), kIPv4CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+ case AF_INET6:
+ aAddress.SetCapacity(kIPv6CStrBufSize);
+ NetAddrToString(&mAddr, aAddress.BeginWriting(), kIPv6CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ aAddress.Assign(mAddr.local.path);
+ break;
+#endif
+ // PR_AF_LOCAL falls through to default when not XP_UNIX
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetPort(uint16_t *aPort)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET:
+ *aPort = ntohs(mAddr.inet.port);
+ break;
+ case AF_INET6:
+ *aPort = ntohs(mAddr.inet6.port);
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ // There is no port number for local / connections.
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFlow(uint32_t *aFlow)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aFlow = ntohl(mAddr.inet6.flowinfo);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetScope(uint32_t *aScope)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aScope = ntohl(mAddr.inet6.scope_id);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetIsV4Mapped(bool *aIsV4Mapped)
+{
+ switch(mAddr.raw.family) {
+ case AF_INET6:
+ *aIsV4Mapped = IPv6ADDR_IS_V4MAPPED(&mAddr.inet6.ip);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetNetAddr(NetAddr *aResult) {
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
diff --git a/netwerk/base/nsNetAddr.h b/netwerk/base/nsNetAddr.h
new file mode 100644
index 000000000..2717596f6
--- /dev/null
+++ b/netwerk/base/nsNetAddr.h
@@ -0,0 +1,31 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetAddr_h__
+#define nsNetAddr_h__
+
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Attributes.h"
+
+class nsNetAddr final : public nsINetAddr
+{
+ ~nsNetAddr() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETADDR
+
+ explicit nsNetAddr(mozilla::net::NetAddr* addr);
+
+private:
+ mozilla::net::NetAddr mAddr;
+
+protected:
+ /* additional members */
+};
+
+#endif // !nsNetAddr_h__
diff --git a/netwerk/base/nsNetSegmentUtils.h b/netwerk/base/nsNetSegmentUtils.h
new file mode 100644
index 000000000..41808eb50
--- /dev/null
+++ b/netwerk/base/nsNetSegmentUtils.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetSegmentUtils_h__
+#define nsNetSegmentUtils_h__
+
+#include "nsIOService.h"
+
+/**
+ * applies defaults to segment params in a consistent way.
+ */
+static inline void
+net_ResolveSegmentParams(uint32_t &segsize, uint32_t &segcount)
+{
+ if (!segsize)
+ segsize = mozilla::net::nsIOService::gDefaultSegmentSize;
+
+ if (!segcount)
+ segcount = mozilla::net::nsIOService::gDefaultSegmentCount;
+}
+
+#endif // !nsNetSegmentUtils_h__
diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp
new file mode 100644
index 000000000..8ff3e788f
--- /dev/null
+++ b/netwerk/base/nsNetUtil.cpp
@@ -0,0 +1,2459 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/LoadContext.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Telemetry.h"
+#include "nsNetUtil.h"
+#include "nsNetUtilInlines.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsCategoryCache.h"
+#include "nsContentUtils.h"
+#include "nsHashKeys.h"
+#include "nsHttp.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptAdapterFactory.h"
+#include "nsIBufferedStreams.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentSniffer.h"
+#include "nsIDocument.h"
+#include "nsIDownloader.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIMutable.h"
+#include "nsINode.h"
+#include "nsIOfflineCacheUpdate.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPropertyBag2.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIRedirectChannelRegistrar.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISimpleStreamListener.h"
+#include "nsISocketProvider.h"
+#include "nsISocketProviderService.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIStreamTransportService.h"
+#include "nsStringStream.h"
+#include "nsISyncStreamListener.h"
+#include "nsITransport.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsIURLParser.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIViewSourceChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "plstr.h"
+#include "nsINestedURI.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "nsIScriptError.h"
+#include "nsISiteSecurityService.h"
+#include "nsHttpHandler.h"
+#include "nsNSSComponent.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#endif
+
+#include <limits>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ MOZ_ASSERT(aLoadingNode);
+ NS_ASSERTION(aTriggeringPrincipal, "Can not create channel without a triggering Principal!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingPrincipal, "Can not create channel without a loading Principal!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+nsresult /* NS_NewChannelNode */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!");
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+nsresult
+NS_MakeAbsoluteURI(nsACString &result,
+ const nsACString &spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ }
+ else if (spec.IsEmpty())
+ rv = baseURI->GetSpec(result);
+ else
+ rv = baseURI->Resolve(spec, result);
+ return rv;
+}
+
+nsresult
+NS_MakeAbsoluteURI(char **result,
+ const char *spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ nsAutoCString resultBuf;
+ rv = NS_MakeAbsoluteURI(resultBuf, nsDependentCString(spec), baseURI);
+ if (NS_SUCCEEDED(rv)) {
+ *result = ToNewCString(resultBuf);
+ if (!*result)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+}
+
+nsresult
+NS_MakeAbsoluteURI(nsAString &result,
+ const nsAString &spec,
+ nsIURI *baseURI)
+{
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ }
+ else {
+ nsAutoCString resultBuf;
+ if (spec.IsEmpty())
+ rv = baseURI->GetSpec(resultBuf);
+ else
+ rv = baseURI->Resolve(NS_ConvertUTF16toUTF8(spec), resultBuf);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF8toUTF16(resultBuf, result);
+ }
+ return rv;
+}
+
+int32_t
+NS_GetDefaultPort(const char *scheme,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> grip;
+ net_EnsureIOService(&ioService, grip);
+ if (!ioService)
+ return -1;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return -1;
+ int32_t port;
+ rv = handler->GetDefaultPort(&port);
+ return NS_SUCCEEDED(rv) ? port : -1;
+}
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool
+NS_StringToACE(const nsACString &idn, nsACString &result)
+{
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv)
+ return false;
+ nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
+ if (NS_FAILED(rv))
+ return false;
+
+ return true;
+}
+
+int32_t
+NS_GetRealPort(nsIURI *aURI)
+{
+ int32_t port;
+ nsresult rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv))
+ return -1;
+
+ if (port != -1)
+ return port; // explicitly specified
+
+ // Otherwise, we have to get the default port from the protocol handler
+
+ // Need the scheme first
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv))
+ return -1;
+
+ return NS_GetDefaultPort(scheme.get());
+}
+
+nsresult /* NS_NewInputStreamChannelWithLoadInfo */
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsILoadInfo *aLoadInfo)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamChannel> isc =
+ do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetURI(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetContentStream(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(isc, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aContentType.IsEmpty()) {
+ rv = channel->SetContentType(aContentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aContentCharset.IsEmpty()) {
+ rv = channel->SetContentCharset(aContentCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel->SetLoadInfo(aLoadInfo);
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aLoadingNode,
+ aSecurityFlags,
+ aContentPolicyType);
+ if (!loadInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aStream,
+ aContentType,
+ aContentCharset,
+ loadInfo);
+}
+
+nsresult /* NS_NewInputStreamChannelPrincipal */
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString &aContentType /* = EmptyCString() */,
+ const nsACString &aContentCharset /* = EmptyCString() */)
+{
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aStream,
+ aContentType,
+ aContentCharset,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType);
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsILoadInfo *aLoadInfo,
+ bool aIsSrcdocChannel /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream;
+ stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef MOZILLA_INTERNAL_API
+ uint32_t len;
+ char* utf8Bytes = ToNewUTF8String(aData, &len);
+ rv = stream->AdoptData(utf8Bytes, len);
+#else
+ char* utf8Bytes = ToNewUTF8String(aData);
+ rv = stream->AdoptData(utf8Bytes, strlen(utf8Bytes));
+#endif
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ aUri,
+ stream,
+ aContentType,
+ NS_LITERAL_CSTRING("UTF-8"),
+ aLoadInfo);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(inStrmChan, NS_ERROR_FAILURE);
+ inStrmChan->SetSrcdocData(aData);
+ }
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(aLoadingPrincipal, aTriggeringPrincipal,
+ aLoadingNode, aSecurityFlags, aContentPolicyType);
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ loadInfo, aIsSrcdocChannel);
+}
+
+nsresult
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */)
+{
+ return NS_NewInputStreamChannelInternal(outChannel,
+ aUri,
+ aData,
+ aContentType,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aIsSrcdocChannel);
+}
+
+nsresult
+NS_NewInputStreamPump(nsIInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos /* = int64_t(-1) */,
+ int64_t streamLen /* = int64_t(-1) */,
+ uint32_t segsize /* = 0 */,
+ uint32_t segcount /* = 0 */,
+ bool closeWhenDone /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamPump> pump =
+ do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->Init(stream, streamPos, streamLen,
+ segsize, segcount, closeWhenDone);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ pump.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result,
+ nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered /* = true */,
+ bool sinkBuffered /* = true */,
+ uint32_t chunkSize /* = 0 */,
+ bool closeSource /* = true */,
+ bool closeSink /* = true */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIAsyncStreamCopier> copier =
+ do_CreateInstance(NS_ASYNCSTREAMCOPIER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = copier->Init(source, sink, target, sourceBuffered, sinkBuffered,
+ chunkSize, closeSource, closeSink);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ copier.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewLoadGroup(nsILoadGroup **result,
+ nsIRequestObserver *obs)
+{
+ nsresult rv;
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = group->SetGroupObserver(obs);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ group.swap(*result);
+ }
+ }
+ return rv;
+}
+
+bool NS_IsReasonableHTTPHeaderValue(const nsACString &aValue)
+{
+ return mozilla::net::nsHttp::IsReasonableHeaderValue(aValue);
+}
+
+bool NS_IsValidHTTPToken(const nsACString &aToken)
+{
+ return mozilla::net::nsHttp::IsValidToken(aToken);
+}
+
+nsresult
+NS_NewLoadGroup(nsILoadGroup **aResult, nsIPrincipal *aPrincipal)
+{
+ using mozilla::LoadContext;
+ nsresult rv;
+
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContext> loadContext = new LoadContext(aPrincipal);
+ rv = group->SetNotificationCallbacks(loadContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ group.forget(aResult);
+ return rv;
+}
+
+bool
+NS_LoadGroupMatchesPrincipal(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal)
+{
+ if (!aPrincipal) {
+ return false;
+ }
+
+ // If this is a null principal then the load group doesn't really matter.
+ // The principal will not be allowed to perform any actions that actually
+ // use the load group. Unconditionally treat null principals as a match.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return true;
+ }
+
+ if (!aLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext),
+ getter_AddRefs(loadContext));
+ NS_ENSURE_TRUE(loadContext, false);
+
+ // Verify load context appId and browser flag match the principal
+ uint32_t contextAppId;
+ bool contextInIsolatedBrowser;
+ nsresult rv = loadContext->GetAppId(&contextAppId);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = loadContext->GetIsInIsolatedMozBrowserElement(&contextInIsolatedBrowser);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return contextAppId == aPrincipal->GetAppId() &&
+ contextInIsolatedBrowser == aPrincipal->GetIsInIsolatedMozBrowserElement();
+}
+
+nsresult
+NS_NewDownloader(nsIStreamListener **result,
+ nsIDownloadObserver *observer,
+ nsIFile *downloadLocation /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDownloader> downloader =
+ do_CreateInstance(NS_DOWNLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = downloader->Init(observer, downloadLocation);
+ if (NS_SUCCEEDED(rv)) {
+ downloader.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewIncrementalStreamLoader(nsIIncrementalStreamLoader **result,
+ nsIIncrementalStreamLoaderObserver *observer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIncrementalStreamLoader> loader =
+ do_CreateInstance(NS_INCREMENTALSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewStreamLoaderInternal(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelInternal(getter_AddRefs(channel),
+ aUri,
+ aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ httpChannel->SetReferrer(aReferrer);
+ }
+ rv = NS_NewStreamLoader(outStream, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return channel->AsyncOpen2(*outStream);
+}
+
+
+nsresult /* NS_NewStreamLoaderNode */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ NS_ASSERTION(aLoadingNode, "Can not create stream loader without a loading Node!");
+ return NS_NewStreamLoaderInternal(outStream,
+ aUri,
+ aObserver,
+ aLoadingNode,
+ aLoadingNode->NodePrincipal(),
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aReferrer);
+}
+
+nsresult /* NS_NewStreamLoaderPrincipal */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIURI *aReferrer /* = nullptr */)
+{
+ return NS_NewStreamLoaderInternal(outStream,
+ aUri,
+ aObserver,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aReferrer);
+}
+
+nsresult
+NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result,
+ nsIUnicharStreamLoaderObserver *observer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUnicharStreamLoader> loader =
+ do_CreateInstance(NS_UNICHARSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSyncStreamListener(nsIStreamListener **result,
+ nsIInputStream **stream)
+{
+ nsresult rv;
+ nsCOMPtr<nsISyncStreamListener> listener =
+ do_CreateInstance(NS_SYNCSTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->GetInputStream(stream);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_ImplementChannelOpen(nsIChannel *channel,
+ nsIInputStream **result)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(channel, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t n;
+ // block until the initial response is received or an error occurs.
+ rv = stream->Available(&n);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = nullptr;
+ stream.swap(*result);
+
+ return NS_OK;
+ }
+
+nsresult
+NS_NewRequestObserverProxy(nsIRequestObserver **result,
+ nsIRequestObserver *observer,
+ nsISupports *context)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRequestObserverProxy> proxy =
+ do_CreateInstance(NS_REQUESTOBSERVERPROXY_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = proxy->Init(observer, context);
+ if (NS_SUCCEEDED(rv)) {
+ proxy.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSimpleStreamListener(nsIStreamListener **result,
+ nsIOutputStream *sink,
+ nsIRequestObserver *observer /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsISimpleStreamListener> listener =
+ do_CreateInstance(NS_SIMPLESTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->Init(sink, observer);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_CheckPortSafety(int32_t port,
+ const char *scheme,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ bool allow;
+ rv = ioService->AllowPort(port, scheme, &allow);
+ if (NS_SUCCEEDED(rv) && !allow) {
+ NS_WARNING("port blocked");
+ rv = NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_CheckPortSafety(nsIURI *uri)
+{
+ int32_t port;
+ nsresult rv = uri->GetPort(&port);
+ if (NS_FAILED(rv) || port == -1) // port undefined or default-valued
+ return NS_OK;
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ return NS_CheckPortSafety(port, scheme.get());
+}
+
+nsresult
+NS_NewProxyInfo(const nsACString &type,
+ const nsACString &host,
+ int32_t port,
+ uint32_t flags,
+ nsIProxyInfo **result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = pps->NewProxyInfo(type, host, port, flags, UINT32_MAX, nullptr,
+ result);
+ return rv;
+}
+
+nsresult
+NS_GetFileProtocolHandler(nsIFileProtocolHandler **result,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_SUCCEEDED(rv))
+ rv = CallQueryInterface(handler, result);
+ }
+ return rv;
+}
+
+nsresult
+NS_GetFileFromURLSpec(const nsACString &inURL,
+ nsIFile **result,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetFileFromURLSpec(inURL, result);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromFile(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromActualFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromActualFile(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetURLSpecFromDir(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv))
+ rv = fileHandler->GetURLSpecFromDir(file, url);
+ return rv;
+}
+
+nsresult
+NS_GetReferrerFromChannel(nsIChannel *channel,
+ nsIURI **referrer)
+{
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ *referrer = nullptr;
+
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel));
+ if (props) {
+ // We have to check for a property on a property bag because the
+ // referrer may be empty for security reasons (for example, when loading
+ // an http page with an https referrer).
+ rv = props->GetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
+ NS_GET_IID(nsIURI),
+ reinterpret_cast<void **>(referrer));
+ if (NS_FAILED(rv))
+ *referrer = nullptr;
+ }
+
+ // if that didn't work, we can still try to get the referrer from the
+ // nsIHttpChannel (if we can QI to it)
+ if (!(*referrer)) {
+ nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel));
+ if (chan) {
+ rv = chan->GetReferrer(referrer);
+ if (NS_FAILED(rv))
+ *referrer = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult
+NS_ParseRequestContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseRequestContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset)
+ contentCharset = charset;
+ return rv;
+}
+
+nsresult
+NS_ParseResponseContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseResponseContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset)
+ contentCharset = charset;
+ return rv;
+}
+
+nsresult
+NS_ExtractCharsetFromContentType(const nsACString &rawContentType,
+ nsCString &contentCharset,
+ bool *hadCharset,
+ int32_t *charsetStart,
+ int32_t *charsetEnd)
+{
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->ExtractCharsetFromContentType(rawContentType,
+ contentCharset,
+ charsetStart,
+ charsetEnd,
+ hadCharset);
+}
+
+nsresult
+NS_NewPartialLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ uint64_t offset,
+ uint64_t length,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPartialFileInputStream> in =
+ do_CreateInstance(NS_PARTIALLOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, offset, length, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ rv = CallQueryInterface(in, result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewAtomicFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewSafeLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewLocalFileStream(nsIFileStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileStream> stream =
+ do_CreateInstance(NS_LOCALFILESTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = stream->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ stream.forget(result);
+ }
+ return rv;
+}
+
+nsresult
+NS_BackgroundInputStream(nsIInputStream **result,
+ nsIInputStream *stream,
+ uint32_t segmentSize /* = 0 */,
+ uint32_t segmentCount /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsITransport> inTransport;
+ rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(inTransport));
+ if (NS_SUCCEEDED(rv))
+ rv = inTransport->OpenInputStream(nsITransport::OPEN_BLOCKING,
+ segmentSize, segmentCount,
+ result);
+ }
+ return rv;
+}
+
+nsresult
+NS_BackgroundOutputStream(nsIOutputStream **result,
+ nsIOutputStream *stream,
+ uint32_t segmentSize /* = 0 */,
+ uint32_t segmentCount /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsITransport> inTransport;
+ rv = sts->CreateOutputTransport(stream, int64_t(-1), int64_t(-1),
+ true, getter_AddRefs(inTransport));
+ if (NS_SUCCEEDED(rv))
+ rv = inTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
+ segmentSize, segmentCount,
+ result);
+ }
+ return rv;
+}
+
+nsresult
+NS_NewBufferedOutputStream(nsIOutputStream **result,
+ nsIOutputStream *str,
+ uint32_t bufferSize)
+{
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> out =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(str, bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ out.forget(result);
+ }
+ }
+ return rv;
+}
+
+already_AddRefed<nsIOutputStream>
+NS_BufferOutputStream(nsIOutputStream *aOutputStream,
+ uint32_t aBufferSize)
+{
+ NS_ASSERTION(aOutputStream, "No output stream given!");
+
+ nsCOMPtr<nsIOutputStream> bos;
+ nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bos), aOutputStream,
+ aBufferSize);
+ if (NS_SUCCEEDED(rv))
+ return bos.forget();
+
+ bos = aOutputStream;
+ return bos.forget();
+}
+
+already_AddRefed<nsIInputStream>
+NS_BufferInputStream(nsIInputStream *aInputStream,
+ uint32_t aBufferSize)
+{
+ NS_ASSERTION(aInputStream, "No input stream given!");
+
+ nsCOMPtr<nsIInputStream> bis;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bis), aInputStream,
+ aBufferSize);
+ if (NS_SUCCEEDED(rv))
+ return bis.forget();
+
+ bis = aInputStream;
+ return bis.forget();
+}
+
+nsresult
+NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream,
+ void **aDest,
+ uint32_t aCount)
+{
+ nsresult rv;
+
+ if (!*aDest) {
+ *aDest = malloc(aCount);
+ if (!*aDest)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char * p = reinterpret_cast<char*>(*aDest);
+ uint32_t bytesRead;
+ uint32_t totalRead = 0;
+ while (1) {
+ rv = aInputStream->Read(p + totalRead, aCount - totalRead, &bytesRead);
+ if (!NS_SUCCEEDED(rv))
+ return rv;
+ totalRead += bytesRead;
+ if (totalRead == aCount)
+ break;
+ // if Read reads 0 bytes, we've hit EOF
+ if (bytesRead == 0)
+ return NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+#ifdef MOZILLA_INTERNAL_API
+
+nsresult
+NS_ReadInputStreamToString(nsIInputStream *aInputStream,
+ nsACString &aDest,
+ uint32_t aCount)
+{
+ if (!aDest.SetLength(aCount, mozilla::fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+ void* dest = aDest.BeginWriting();
+ return NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount);
+}
+
+#endif
+
+nsresult
+NS_LoadPersistentPropertiesFromURISpec(nsIPersistentProperties **outResult,
+ const nsACString &aSpec)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPersistentProperties> properties =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = properties->Load(in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ properties.swap(*outResult);
+ return NS_OK;
+}
+
+bool
+NS_UsePrivateBrowsing(nsIChannel *channel)
+{
+ bool isPrivate = false;
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ if (pbChannel && NS_SUCCEEDED(pbChannel->GetIsChannelPrivate(&isPrivate))) {
+ return isPrivate;
+ }
+
+ // Some channels may not implement nsIPrivateBrowsingChannel
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+bool
+NS_GetOriginAttributes(nsIChannel *aChannel,
+ mozilla::NeckoOriginAttributes &aAttributes)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (!loadInfo) {
+ return false;
+ }
+
+ loadInfo->GetOriginAttributes(&aAttributes);
+ aAttributes.SyncAttributesWithPrivateBrowsing(NS_UsePrivateBrowsing(aChannel));
+ return true;
+}
+
+bool
+NS_GetAppInfo(nsIChannel *aChannel,
+ uint32_t *aAppID,
+ bool *aIsInIsolatedMozBrowserElement)
+{
+ NeckoOriginAttributes attrs;
+
+ if (!NS_GetOriginAttributes(aChannel, attrs)) {
+ return false;
+ }
+
+ *aAppID = attrs.mAppId;
+ *aIsInIsolatedMozBrowserElement = attrs.mInIsolatedMozBrowser;
+
+ return true;
+}
+
+bool
+NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ MOZ_RELEASE_ASSERT(loadInfo, "Origin tracking only works for channels created with a loadinfo");
+
+#ifdef DEBUG
+ // Don't enforce TYPE_DOCUMENT assertions for loads
+ // initiated by javascript tests.
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ MOZ_ASSERT(skipContentTypeCheck ||
+ loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT,
+ "calling NS_HasBeenCrossOrigin on a top level load");
+
+ // Always treat tainted channels as cross-origin.
+ if (loadInfo->GetTainting() != LoadTainting::Basic) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->LoadingPrincipal();
+ uint32_t mode = loadInfo->GetSecurityMode();
+ bool dataInherits =
+ mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS ||
+ mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS ||
+ mode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+
+ bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits();
+
+ for (nsIPrincipal* principal : loadInfo->RedirectChain()) {
+ nsCOMPtr<nsIURI> uri;
+ principal->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ continue;
+ }
+
+ if (NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits))) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ return false;
+ }
+
+ return NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits));
+}
+
+nsresult
+NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject,
+ uint32_t *aAppID,
+ bool *aBrowserOnly)
+{
+ nsresult rv;
+
+ nsCOMPtr<mozIApplicationClearPrivateDataParams>
+ clearParams(do_QueryInterface(aSubject));
+ MOZ_ASSERT(clearParams);
+ if (!clearParams) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t appId;
+ rv = clearParams->GetAppId(&appId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(appId != NECKO_UNKNOWN_APP_ID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (appId == NECKO_UNKNOWN_APP_ID) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool browserOnly = false;
+ rv = clearParams->GetBrowserOnly(&browserOnly);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aAppID = appId;
+ *aBrowserOnly = browserOnly;
+ return NS_OK;
+}
+
+bool
+NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing)
+{
+ if (usePrivateBrowsing) {
+ return false;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+ do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+ if (!offlineService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv = offlineService->OfflineAppAllowedForURI(aURI,
+ nullptr,
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+bool
+NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing)
+{
+ if (usePrivateBrowsing) {
+ return false;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+ do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+ if (!offlineService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv = offlineService->OfflineAppAllowed(aPrincipal,
+ nullptr,
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+void
+NS_WrapAuthPrompt(nsIAuthPrompt *aAuthPrompt,
+ nsIAuthPrompt2 **aAuthPrompt2)
+{
+ nsCOMPtr<nsIAuthPromptAdapterFactory> factory =
+ do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID);
+ if (!factory)
+ return;
+
+ NS_WARNING("Using deprecated nsIAuthPrompt");
+ factory->CreateAdapter(aAuthPrompt, aAuthPrompt2);
+}
+
+void
+NS_QueryAuthPrompt2(nsIInterfaceRequestor *aCallbacks,
+ nsIAuthPrompt2 **aAuthPrompt)
+{
+ CallGetInterface(aCallbacks, aAuthPrompt);
+ if (*aAuthPrompt)
+ return;
+
+ // Maybe only nsIAuthPrompt is provided and we have to wrap it.
+ nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks));
+ if (!prompt)
+ return;
+
+ NS_WrapAuthPrompt(prompt, aAuthPrompt);
+}
+
+void
+NS_QueryAuthPrompt2(nsIChannel *aChannel,
+ nsIAuthPrompt2 **aAuthPrompt)
+{
+ *aAuthPrompt = nullptr;
+
+ // We want to use any auth prompt we can find on the channel's callbacks,
+ // and if that fails use the loadgroup's prompt (if any)
+ // Therefore, we can't just use NS_QueryNotificationCallbacks, because
+ // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's
+ // nsIAuthPrompt.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+ if (*aAuthPrompt)
+ return;
+ }
+
+ nsCOMPtr<nsILoadGroup> group;
+ aChannel->GetLoadGroup(getter_AddRefs(group));
+ if (!group)
+ return;
+
+ group->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks)
+ return;
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+}
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor **result)
+{
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup)
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result);
+}
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIInterfaceRequestor **result)
+{
+ return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr, result);
+}
+
+nsresult
+NS_DoImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result)
+{
+ NS_PRECONDITION(nestedURI, "Must have a nested URI!");
+ NS_PRECONDITION(!*result, "Must have null *result");
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = nestedURI->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We may need to loop here until we reach the innermost
+ // URI.
+ nsCOMPtr<nsINestedURI> nestedInner(do_QueryInterface(inner));
+ while (nestedInner) {
+ rv = nestedInner->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nestedInner = do_QueryInterface(inner);
+ }
+
+ // Found the innermost one if we reach here.
+ inner.swap(*result);
+
+ return rv;
+}
+
+nsresult
+NS_ImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result)
+{
+ // Make it safe to use swap()
+ *result = nullptr;
+
+ return NS_DoImplGetInnermostURI(nestedURI, result);
+}
+
+nsresult
+NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result)
+{
+ NS_PRECONDITION(uri, "Must have a URI");
+
+ // Assume mutable until told otherwise
+ bool isMutable = true;
+ nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri));
+ if (mutableObj) {
+ nsresult rv = mutableObj->GetMutable(&isMutable);
+ isMutable = NS_FAILED(rv) || isMutable;
+ }
+
+ if (!isMutable) {
+ NS_ADDREF(*result = uri);
+ return NS_OK;
+ }
+
+ nsresult rv = uri->Clone(result);
+ if (NS_SUCCEEDED(rv) && !*result) {
+ NS_ERROR("nsIURI.clone contract was violated");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+void
+NS_TryToSetImmutable(nsIURI *uri)
+{
+ nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri));
+ if (mutableObj) {
+ mutableObj->SetMutable(false);
+ }
+}
+
+already_AddRefed<nsIURI>
+NS_TryToMakeImmutable(nsIURI *uri,
+ nsresult *outRv /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+
+ nsCOMPtr<nsIURI> result;
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(util, "do_GetNetUtil lied");
+ rv = util->ToImmutableURI(uri, getter_AddRefs(result));
+ }
+
+ if (NS_FAILED(rv)) {
+ result = uri;
+ }
+
+ if (outRv) {
+ *outRv = rv;
+ }
+
+ return result.forget();
+}
+
+already_AddRefed<nsIURI>
+NS_GetInnermostURI(nsIURI *aURI)
+{
+ NS_PRECONDITION(aURI, "Must have URI");
+
+ nsCOMPtr<nsIURI> uri = aURI;
+
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(uri));
+ if (!nestedURI) {
+ return uri.forget();
+ }
+
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+nsresult
+NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri)
+{
+ *uri = nullptr;
+ nsLoadFlags loadFlags = 0;
+ nsresult rv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (loadFlags & nsIChannel::LOAD_REPLACE) {
+ return channel->GetURI(uri);
+ }
+
+ return channel->GetOriginalURI(uri);
+}
+
+uint32_t
+NS_SecurityHashURI(nsIURI *aURI)
+{
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+
+ nsAutoCString scheme;
+ uint32_t schemeHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetScheme(scheme)))
+ schemeHash = mozilla::HashString(scheme);
+
+ // TODO figure out how to hash file:// URIs
+ if (scheme.EqualsLiteral("file"))
+ return schemeHash; // sad face
+
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(baseURI,
+ nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag)
+ {
+ nsAutoCString spec;
+ uint32_t specHash;
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_SUCCEEDED(res))
+ specHash = mozilla::HashString(spec);
+ else
+ specHash = static_cast<uint32_t>(res);
+ return specHash;
+ }
+
+ nsAutoCString host;
+ uint32_t hostHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetAsciiHost(host)))
+ hostHash = mozilla::HashString(host);
+
+ return mozilla::AddToHash(schemeHash, hostHash, NS_GetRealPort(baseURI));
+}
+
+bool
+NS_SecurityCompareURIs(nsIURI *aSourceURI,
+ nsIURI *aTargetURI,
+ bool aStrictFileOriginPolicy)
+{
+ // Note that this is not an Equals() test on purpose -- for URIs that don't
+ // support host/port, we want equality to basically be object identity, for
+ // security purposes. Otherwise, for example, two javascript: URIs that
+ // are otherwise unrelated could end up "same origin", which would be
+ // unfortunate.
+ if (aSourceURI && aSourceURI == aTargetURI)
+ {
+ return true;
+ }
+
+ if (!aTargetURI || !aSourceURI)
+ {
+ return false;
+ }
+
+ // If either URI is a nested URI, get the base URI
+ nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
+ nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+ // If either uri is an nsIURIWithPrincipal
+ nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(sourceBaseURI);
+ if (uriPrinc) {
+ uriPrinc->GetPrincipalUri(getter_AddRefs(sourceBaseURI));
+ }
+
+ uriPrinc = do_QueryInterface(targetBaseURI);
+ if (uriPrinc) {
+ uriPrinc->GetPrincipalUri(getter_AddRefs(targetBaseURI));
+ }
+
+ if (!sourceBaseURI || !targetBaseURI)
+ return false;
+
+ // Compare schemes
+ nsAutoCString targetScheme;
+ bool sameScheme = false;
+ if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) ||
+ NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) ||
+ !sameScheme)
+ {
+ // Not same-origin if schemes differ
+ return false;
+ }
+
+ // For file scheme, reject unless the files are identical. See
+ // NS_RelaxStrictFileOriginPolicy for enforcing file same-origin checking
+ if (targetScheme.EqualsLiteral("file"))
+ {
+ // in traditional unsafe behavior all files are the same origin
+ if (!aStrictFileOriginPolicy)
+ return true;
+
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
+
+ if (!sourceFileURL || !targetFileURL)
+ return false;
+
+ nsCOMPtr<nsIFile> sourceFile, targetFile;
+
+ sourceFileURL->GetFile(getter_AddRefs(sourceFile));
+ targetFileURL->GetFile(getter_AddRefs(targetFile));
+
+ if (!sourceFile || !targetFile)
+ return false;
+
+ // Otherwise they had better match
+ bool filesAreEqual = false;
+ nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual);
+ return NS_SUCCEEDED(rv) && filesAreEqual;
+ }
+
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(targetBaseURI,
+ nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag)
+ {
+ // URIs with this flag have the whole spec as a distinct trust
+ // domain; use the whole spec for comparison
+ nsAutoCString targetSpec;
+ nsAutoCString sourceSpec;
+ return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) &&
+ NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) &&
+ targetSpec.Equals(sourceSpec) );
+ }
+
+ // Compare hosts
+ nsAutoCString targetHost;
+ nsAutoCString sourceHost;
+ if (NS_FAILED( targetBaseURI->GetAsciiHost(targetHost) ) ||
+ NS_FAILED( sourceBaseURI->GetAsciiHost(sourceHost) ))
+ {
+ return false;
+ }
+
+ nsCOMPtr<nsIStandardURL> targetURL(do_QueryInterface(targetBaseURI));
+ nsCOMPtr<nsIStandardURL> sourceURL(do_QueryInterface(sourceBaseURI));
+ if (!targetURL || !sourceURL)
+ {
+ return false;
+ }
+
+#ifdef MOZILLA_INTERNAL_API
+ if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator() ))
+#else
+ if (!targetHost.Equals(sourceHost, CaseInsensitiveCompare))
+#endif
+ {
+ return false;
+ }
+
+ return NS_GetRealPort(targetBaseURI) == NS_GetRealPort(sourceBaseURI);
+}
+
+bool
+NS_URIIsLocalFile(nsIURI *aURI)
+{
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil();
+
+ bool isFile;
+ return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI,
+ nsIProtocolHandler::URI_IS_LOCAL_FILE,
+ &isFile)) &&
+ isFile;
+}
+
+bool
+NS_RelaxStrictFileOriginPolicy(nsIURI *aTargetURI,
+ nsIURI *aSourceURI,
+ bool aAllowDirectoryTarget /* = false */)
+{
+ if (!NS_URIIsLocalFile(aTargetURI)) {
+ // This is probably not what the caller intended
+ NS_NOTREACHED("NS_RelaxStrictFileOriginPolicy called with non-file URI");
+ return false;
+ }
+
+ if (!NS_URIIsLocalFile(aSourceURI)) {
+ // If the source is not also a file: uri then forget it
+ // (don't want resource: principals in a file: doc)
+ //
+ // note: we're not de-nesting jar: uris here, we want to
+ // keep archive content bottled up in its own little island
+ return false;
+ }
+
+ //
+ // pull out the internal files
+ //
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(aTargetURI));
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(aSourceURI));
+ nsCOMPtr<nsIFile> targetFile;
+ nsCOMPtr<nsIFile> sourceFile;
+ bool targetIsDir;
+
+ // Make sure targetFile is not a directory (bug 209234)
+ // and that it exists w/out unescaping (bug 395343)
+ if (!sourceFileURL || !targetFileURL ||
+ NS_FAILED(targetFileURL->GetFile(getter_AddRefs(targetFile))) ||
+ NS_FAILED(sourceFileURL->GetFile(getter_AddRefs(sourceFile))) ||
+ !targetFile || !sourceFile ||
+ NS_FAILED(targetFile->Normalize()) ||
+#ifndef MOZ_WIDGET_ANDROID
+ NS_FAILED(sourceFile->Normalize()) ||
+#endif
+ (!aAllowDirectoryTarget &&
+ (NS_FAILED(targetFile->IsDirectory(&targetIsDir)) || targetIsDir))) {
+ return false;
+ }
+
+ //
+ // If the file to be loaded is in a subdirectory of the source
+ // (or same-dir if source is not a directory) then it will
+ // inherit its source principal and be scriptable by that source.
+ //
+ bool sourceIsDir;
+ bool allowed = false;
+ nsresult rv = sourceFile->IsDirectory(&sourceIsDir);
+ if (NS_SUCCEEDED(rv) && sourceIsDir) {
+ rv = sourceFile->Contains(targetFile, &allowed);
+ } else {
+ nsCOMPtr<nsIFile> sourceParent;
+ rv = sourceFile->GetParent(getter_AddRefs(sourceParent));
+ if (NS_SUCCEEDED(rv) && sourceParent) {
+ rv = sourceParent->Equals(targetFile, &allowed);
+ if (NS_FAILED(rv) || !allowed) {
+ rv = sourceParent->Contains(targetFile, &allowed);
+ } else {
+ MOZ_ASSERT(aAllowDirectoryTarget,
+ "sourceFile->Parent == targetFile, but targetFile "
+ "should've been disallowed if it is a directory");
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && allowed) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+NS_IsInternalSameURIRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res;
+}
+
+bool
+NS_IsHSTSUpgradeRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags)
+{
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool isHttp;
+ if (NS_FAILED(oldURI->SchemeIs("http", &isHttp)) || !isHttp) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(oldURI, getter_AddRefs(upgradedURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res;
+}
+
+nsresult
+NS_LinkRedirectChannels(uint32_t channelId,
+ nsIParentChannel *parentChannel,
+ nsIChannel **_result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return registrar->LinkChannels(channelId,
+ parentChannel,
+ _result);
+}
+
+#define NS_FAKE_SCHEME "http://"
+#define NS_FAKE_TLD ".invalid"
+nsresult NS_MakeRandomInvalidURLString(nsCString &result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID idee;
+ rv = uuidgen->GenerateUUIDInPlace(&idee);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char chars[NSID_LENGTH];
+ idee.ToProvidedString(chars);
+
+ result.AssignLiteral(NS_FAKE_SCHEME);
+ // Strip off the '{' and '}' at the beginning and end of the UUID
+ result.Append(chars + 1, NSID_LENGTH - 3);
+ result.AppendLiteral(NS_FAKE_TLD);
+
+ return NS_OK;
+}
+#undef NS_FAKE_SCHEME
+#undef NS_FAKE_TLD
+
+nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
+ nsIInputStream **aStream)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetSecurityMode() != 0) {
+ return aChannel->Open2(aStream);
+ }
+ return aChannel->Open(aStream);
+}
+
+nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel,
+ nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetSecurityMode() != 0) {
+ return aChannel->AsyncOpen2(aListener);
+ }
+ return aChannel->AsyncOpen(aListener, nullptr);
+}
+
+nsresult
+NS_CheckIsJavaCompatibleURLString(nsCString &urlString, bool *result)
+{
+ *result = false; // Default to "no"
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !urlParser)
+ return NS_ERROR_FAILURE;
+
+ bool compatible = true;
+ uint32_t schemePos = 0;
+ int32_t schemeLen = 0;
+ urlParser->ParseURL(urlString.get(), -1, &schemePos, &schemeLen,
+ nullptr, nullptr, nullptr, nullptr);
+ if (schemeLen != -1) {
+ nsCString scheme;
+ scheme.Assign(urlString.get() + schemePos, schemeLen);
+ // By default Java only understands a small number of URL schemes, and of
+ // these only some can legitimately represent a browser page's "origin"
+ // (and be something we can legitimately expect Java to handle ... or not
+ // to mishandle).
+ //
+ // Besides those listed below, the OJI plugin understands the "jar",
+ // "mailto", "netdoc", "javascript" and "rmi" schemes, and Java Plugin2
+ // also understands the "about" scheme. We actually pass "about" URLs
+ // to Java ("about:blank" when processing a javascript: URL (one that
+ // calls Java) from the location bar of a blank page, and (in FF4 and up)
+ // "about:home" when processing a javascript: URL from the home page).
+ // And Java doesn't appear to mishandle them (for example it doesn't allow
+ // connections to "about" URLs). But it doesn't make any sense to do
+ // same-origin checks on "about" URLs, so we don't include them in our
+ // scheme whitelist.
+ //
+ // The OJI plugin doesn't understand "chrome" URLs (only Java Plugin2
+ // does) -- so we mustn't pass them to the OJI plugin. But we do need to
+ // pass "chrome" URLs to Java Plugin2: Java Plugin2 grants additional
+ // privileges to chrome "origins", and some extensions take advantage of
+ // this. For more information see bug 620773.
+ //
+ // As of FF4, we no longer support the OJI plugin.
+ if (PL_strcasecmp(scheme.get(), "http") &&
+ PL_strcasecmp(scheme.get(), "https") &&
+ PL_strcasecmp(scheme.get(), "file") &&
+ PL_strcasecmp(scheme.get(), "ftp") &&
+ PL_strcasecmp(scheme.get(), "gopher") &&
+ PL_strcasecmp(scheme.get(), "chrome"))
+ compatible = false;
+ } else {
+ compatible = false;
+ }
+
+ *result = compatible;
+
+ return NS_OK;
+}
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t
+NS_GetContentDispositionFromToken(const nsAString &aDispToken)
+{
+ // RFC 2183, section 2.8 says that an unknown disposition
+ // value should be treated as "attachment"
+ // If all of these tests eval to false, then we have a content-disposition of
+ // "attachment" or unknown
+ if (aDispToken.IsEmpty() ||
+ aDispToken.LowerCaseEqualsLiteral("inline") ||
+ // Broken sites just send
+ // Content-Disposition: filename="file"
+ // without a disposition token... screen those out.
+ StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename"))
+ return nsIChannel::DISPOSITION_INLINE;
+
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+}
+
+uint32_t
+NS_GetContentDispositionFromHeader(const nsACString &aHeader,
+ nsIChannel *aChan /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+
+ nsAutoCString fallbackCharset;
+ if (aChan) {
+ nsCOMPtr<nsIURI> uri;
+ aChan->GetURI(getter_AddRefs(uri));
+ if (uri)
+ uri->GetOriginCharset(fallbackCharset);
+ }
+
+ nsAutoString dispToken;
+ rv = mimehdrpar->GetParameterHTTP(aHeader, "", fallbackCharset, true, nullptr,
+ dispToken);
+
+ if (NS_FAILED(rv)) {
+ // special case (see bug 272541): empty disposition type handled as "inline"
+ if (rv == NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY)
+ return nsIChannel::DISPOSITION_INLINE;
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+ }
+
+ return NS_GetContentDispositionFromToken(dispToken);
+}
+
+nsresult
+NS_GetFilenameFromDisposition(nsAString &aFilename,
+ const nsACString &aDisposition,
+ nsIURI *aURI /* = nullptr */)
+{
+ aFilename.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+
+ nsAutoCString fallbackCharset;
+ if (url)
+ url->GetOriginCharset(fallbackCharset);
+ // Get the value of 'filename' parameter
+ rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename",
+ fallbackCharset, true, nullptr,
+ aFilename);
+
+ if (NS_FAILED(rv)) {
+ aFilename.Truncate();
+ return rv;
+ }
+
+ if (aFilename.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+void net_EnsurePSMInit()
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> psm = do_GetService(PSM_COMPONENT_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+bool NS_IsAboutBlank(nsIURI *uri)
+{
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ bool isAbout = false;
+ if (NS_FAILED(uri->SchemeIs("about", &isAbout)) || !isAbout) {
+ return false;
+ }
+
+ return uri->GetSpecOrDefault().EqualsLiteral("about:blank");
+}
+
+nsresult
+NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString &hostLine)
+{
+ if (strchr(host.get(), ':')) {
+ // host is an IPv6 address literal and must be encapsulated in []'s
+ hostLine.Assign('[');
+ // scope id is not needed for Host header.
+ int scopeIdPos = host.FindChar('%');
+ if (scopeIdPos == -1)
+ hostLine.Append(host);
+ else if (scopeIdPos > 0)
+ hostLine.Append(Substring(host, 0, scopeIdPos));
+ else
+ return NS_ERROR_MALFORMED_URI;
+ hostLine.Append(']');
+ }
+ else
+ hostLine.Assign(host);
+ if (port != -1) {
+ hostLine.Append(':');
+ hostLine.AppendInt(port);
+ }
+ return NS_OK;
+}
+
+void
+NS_SniffContent(const char *aSnifferType, nsIRequest *aRequest,
+ const uint8_t *aData, uint32_t aLength,
+ nsACString &aSniffedType)
+{
+ typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
+ extern ContentSnifferCache* gNetSniffers;
+ extern ContentSnifferCache* gDataSniffers;
+ ContentSnifferCache* cache = nullptr;
+ if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) {
+ if (!gNetSniffers) {
+ gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY);
+ }
+ cache = gNetSniffers;
+ } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) {
+ if (!gDataSniffers) {
+ gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY);
+ }
+ cache = gDataSniffers;
+ } else {
+ // Invalid content sniffer type was requested
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ nsCOMArray<nsIContentSniffer> sniffers;
+ cache->GetEntries(sniffers);
+ for (int32_t i = 0; i < sniffers.Count(); ++i) {
+ nsresult rv = sniffers[i]->GetMIMETypeFromContent(aRequest, aData, aLength, aSniffedType);
+ if (NS_SUCCEEDED(rv) && !aSniffedType.IsEmpty()) {
+ return;
+ }
+ }
+
+ aSniffedType.Truncate();
+}
+
+bool
+NS_IsSrcdocChannel(nsIChannel *aChannel)
+{
+ bool isSrcdoc;
+ nsCOMPtr<nsIInputStreamChannel> isr = do_QueryInterface(aChannel);
+ if (isr) {
+ isr->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(aChannel);
+ if (vsc) {
+ vsc->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ return false;
+}
+
+nsresult
+NS_ShouldSecureUpgrade(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aChannelResultPrincipal,
+ bool aPrivateBrowsing,
+ bool aAllowSTS,
+ bool& aShouldUpgrade)
+{
+ // Even if we're in private browsing mode, we still enforce existing STS
+ // data (it is read-only).
+ // if the connection is not using SSL and either the exact host matches or
+ // a superdomain wants to force HTTPS, do it.
+ bool isHttps = false;
+ nsresult rv = aURI->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isHttps) {
+ // If any of the documents up the chain to the root doucment makes use of
+ // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill
+ // the promise to CSP and mixed content blocking to upgrade the channel
+ // from http to https.
+ if (aLoadInfo) {
+ // Please note that cross origin top level navigations are not subject
+ // to upgrade-insecure-requests, see:
+ // http://www.w3.org/TR/upgrade-insecure-requests/#examples
+ // Compare the principal we are navigating to (aChannelResultPrincipal)
+ // with the referring/triggering Principal.
+ bool crossOriginNavigation =
+ (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) &&
+ (!aChannelResultPrincipal->Equals(aLoadInfo->TriggeringPrincipal()));
+
+ if (aLoadInfo->GetUpgradeInsecureRequests() && !crossOriginNavigation) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendASCII("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+
+ const char16_t* params[] = { reportSpec.get(), reportScheme.get() };
+ uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ CSP_LogLocalizedStr(u"upgradeInsecureRequest",
+ params, ArrayLength(params),
+ EmptyString(), // aSourceFile
+ EmptyString(), // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::warningFlag, "CSP",
+ innerWindowId);
+
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4);
+ aShouldUpgrade = true;
+ return NS_OK;
+ }
+ }
+
+ // enforce Strict-Transport-Security
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ bool isStsHost = false;
+ uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
+ nullptr, &isStsHost);
+
+ // if the SSS check fails, it's likely because this load is on a
+ // malformed URI or something else in the setup is wrong, so any error
+ // should be reported.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isStsHost) {
+ LOG(("nsHttpChannel::Connect() STS permissions found\n"));
+ if (aAllowSTS) {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+ aShouldUpgrade = true;
+ return NS_OK;
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+ }
+ aShouldUpgrade = false;
+ return NS_OK;
+}
+
+nsresult
+NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI)
+{
+ nsCOMPtr<nsIURI> upgradedURI;
+
+ nsresult rv = aURI->Clone(getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Change the scheme to HTTPS:
+ upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
+
+ // Change the default port to 443:
+ nsCOMPtr<nsIStandardURL> upgradedStandardURL = do_QueryInterface(upgradedURI);
+ if (upgradedStandardURL) {
+ upgradedStandardURL->SetDefaultPort(443);
+ } else {
+ // If we don't have a nsStandardURL, fall back to using GetPort/SetPort.
+ // XXXdholbert Is this function even called with a non-nsStandardURL arg,
+ // in practice?
+ int32_t oldPort = -1;
+ rv = aURI->GetPort(&oldPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Keep any nonstandard ports so only the scheme is changed.
+ // For example:
+ // http://foo.com:80 -> https://foo.com:443
+ // http://foo.com:81 -> https://foo.com:81
+
+ if (oldPort == 80 || oldPort == -1) {
+ upgradedURI->SetPort(-1);
+ } else {
+ upgradedURI->SetPort(oldPort);
+ }
+ }
+
+ upgradedURI.forget(aUpgradedURI);
+ return NS_OK;
+}
+
+nsresult
+NS_CompareLoadInfoAndLoadContext(nsIChannel *aChannel)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadInfo || !loadContext) {
+ return NS_OK;
+ }
+
+ // We try to skip about:newtab and about:sync-tabs.
+ // about:newtab will use SystemPrincipal to download thumbnails through
+ // https:// and blob URLs.
+ // about:sync-tabs will fetch icons through moz-icon://.
+ bool isAboutPage = false;
+ nsINode* node = loadInfo->LoadingNode();
+ if (node) {
+ nsIDocument* doc = node->OwnerDoc();
+ if (doc) {
+ nsIURI* uri = doc->GetDocumentURI();
+ nsresult rv = uri->SchemeIs("about", &isAboutPage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (isAboutPage) {
+ return NS_OK;
+ }
+
+ // We skip the favicon loading here. The favicon loading might be
+ // triggered by the XUL image. For that case, the loadContext will have
+ // default originAttributes since the XUL image uses SystemPrincipal, but
+ // the loadInfo will use originAttributes from the content. Thus, the
+ // originAttributes between loadInfo and loadContext will be different.
+ // That's why we have to skip the comparison for the favicon loading.
+ if (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) &&
+ loadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return NS_OK;
+ }
+
+ uint32_t loadContextAppId = 0;
+ nsresult rv = loadContext->GetAppId(&loadContextAppId);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool loadContextIsInBE = false;
+ rv = loadContext->GetIsInIsolatedMozBrowserElement(&loadContextIsInBE);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes();
+ DocShellOriginAttributes originAttrsLoadContext;
+ loadContext->GetOriginAttributes(originAttrsLoadContext);
+
+ LOG(("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d, %d, %d; "
+ "loadContext: %d %d, %d, %d. [channel=%p]",
+ originAttrsLoadInfo.mAppId, originAttrsLoadInfo.mInIsolatedMozBrowser,
+ originAttrsLoadInfo.mUserContextId, originAttrsLoadInfo.mPrivateBrowsingId,
+ loadContextAppId, loadContextIsInBE,
+ originAttrsLoadContext.mUserContextId, originAttrsLoadContext.mPrivateBrowsingId,
+ aChannel));
+
+ MOZ_ASSERT(originAttrsLoadInfo.mAppId == loadContextAppId,
+ "AppId in the loadContext and in the loadInfo are not the "
+ "same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mInIsolatedMozBrowser ==
+ loadContextIsInBE,
+ "The value of InIsolatedMozBrowser in the loadContext and in "
+ "the loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mUserContextId ==
+ originAttrsLoadContext.mUserContextId,
+ "The value of mUserContextId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId ==
+ originAttrsLoadContext.mPrivateBrowsingId,
+ "The value of mPrivateBrowsingId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ return NS_OK;
+}
+
+namespace mozilla {
+namespace net {
+
+bool
+InScriptableRange(int64_t val)
+{
+ return (val <= kJS_MAX_SAFE_INTEGER) && (val >= kJS_MIN_SAFE_INTEGER);
+}
+
+bool
+InScriptableRange(uint64_t val)
+{
+ return val <= kJS_MAX_SAFE_UINTEGER;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h
new file mode 100644
index 000000000..bd89ca8ae
--- /dev/null
+++ b/netwerk/base/nsNetUtil.h
@@ -0,0 +1,1000 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_h__
+#define nsNetUtil_h__
+
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "mozilla/Services.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+class nsIURI;
+class nsIPrincipal;
+class nsIAsyncStreamCopier;
+class nsIAuthPrompt;
+class nsIAuthPrompt2;
+class nsIChannel;
+class nsIChannelPolicy;
+class nsIDownloadObserver;
+class nsIEventTarget;
+class nsIFileProtocolHandler;
+class nsIFileStream;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsIInterfaceRequestor;
+class nsINestedURI;
+class nsINetworkInterface;
+class nsIOutputStream;
+class nsIParentChannel;
+class nsIPersistentProperties;
+class nsIProxyInfo;
+class nsIRequestObserver;
+class nsIStreamListener;
+class nsIStreamLoader;
+class nsIStreamLoaderObserver;
+class nsIIncrementalStreamLoader;
+class nsIIncrementalStreamLoaderObserver;
+class nsIUnicharStreamLoader;
+class nsIUnicharStreamLoaderObserver;
+
+namespace mozilla { class NeckoOriginAttributes; }
+
+template <class> class nsCOMPtr;
+template <typename> struct already_AddRefed;
+
+#ifdef MOZILLA_INTERNAL_API
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#else
+#include "nsStringAPI.h"
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+already_AddRefed<nsIIOService> do_GetIOService(nsresult *error = 0);
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult *error = 0);
+
+#else
+// Helper, to simplify getting the I/O service.
+const nsGetServiceByContractIDWithError do_GetIOService(nsresult *error = 0);
+
+// An alias to do_GetIOService
+const nsGetServiceByContractIDWithError do_GetNetUtil(nsresult *error = 0);
+
+#endif
+
+// private little helper function... don't call this directly!
+nsresult net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip);
+
+nsresult NS_NewURI(nsIURI **result,
+ const nsACString &spec,
+ const char *charset = nullptr,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewURI(nsIURI **result,
+ const nsAString &spec,
+ const char *charset = nullptr,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewURI(nsIURI **result,
+ const char *spec,
+ nsIURI *baseURI = nullptr,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+nsresult NS_NewFileURI(nsIURI **result,
+ nsIFile *spec,
+ nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers
+
+/*
+* How to create a new Channel, using NS_NewChannel,
+* NS_NewChannelWithTriggeringPrincipal,
+* NS_NewInputStreamChannel, NS_NewChannelInternal
+* and it's variations:
+*
+* What specific API function to use:
+* * The NS_NewChannelInternal functions should almost never be directly
+* called outside of necko code.
+* * If possible, use NS_NewChannel() providing a loading *nsINode*
+* * If no loading *nsINode* is avaialable, call NS_NewChannel() providing
+* a loading *nsIPrincipal*.
+* * Call NS_NewChannelWithTriggeringPrincipal if the triggeringPrincipal
+* is different from the loadingPrincipal.
+* * Call NS_NewChannelInternal() providing aLoadInfo object in cases where
+* you already have loadInfo object, e.g in case of a channel redirect.
+*
+* @param aURI
+* nsIURI from which to make a channel
+* @param aLoadingNode
+* @param aLoadingPrincipal
+* @param aTriggeringPrincipal
+* @param aSecurityFlags
+* @param aContentPolicyType
+* These will be used as values for the nsILoadInfo object on the
+* created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+*
+* Please note, if you provide both a loadingNode and a loadingPrincipal,
+* then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+* But less error prone is to just supply a loadingNode.
+*
+* Keep in mind that URIs coming from a webpage should *never* use the
+* systemPrincipal as the loadingPrincipal.
+*/
+nsresult NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsILoadInfo *aLoadInfo,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /* NS_NewChannelNode */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /* NS_NewChannelPrincipal */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService *aIoService = nullptr);
+
+nsresult NS_MakeAbsoluteURI(nsACString &result,
+ const nsACString &spec,
+ nsIURI *baseURI);
+
+nsresult NS_MakeAbsoluteURI(char **result,
+ const char *spec,
+ nsIURI *baseURI);
+
+nsresult NS_MakeAbsoluteURI(nsAString &result,
+ const nsAString &spec,
+ nsIURI *baseURI);
+
+/**
+ * This function is a helper function to get a scheme's default port.
+ */
+int32_t NS_GetDefaultPort(const char *scheme,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString &idn, nsACString &result);
+
+/**
+ * This function is a helper function to get a protocol's default port if the
+ * URI does not specify a port explicitly. Returns -1 if this protocol has no
+ * concept of ports or if there was an error getting the port.
+ */
+int32_t NS_GetRealPort(nsIURI *aURI);
+
+nsresult /* NS_NewInputStreamChannelWithLoadInfo */
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsILoadInfo *aLoadInfo);
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ const nsACString &aContentType,
+ const nsACString &aContentCharset,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType);
+
+
+nsresult /* NS_NewInputStreamChannelPrincipal */
+NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIInputStream *aStream,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString &aContentType = EmptyCString(),
+ const nsACString &aContentCharset = EmptyCString());
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult
+NS_NewInputStreamChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsILoadInfo *aLoadInfo,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ const nsAString &aData,
+ const nsACString &aContentType,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamPump(nsIInputStreamPump **result,
+ nsIInputStream *stream,
+ int64_t streamPos = int64_t(-1),
+ int64_t streamLen = int64_t(-1),
+ uint32_t segsize = 0,
+ uint32_t segcount = 0,
+ bool closeWhenDone = false);
+
+// NOTE: you will need to specify whether or not your streams are buffered
+// (i.e., do they implement ReadSegments/WriteSegments). the default
+// assumption of TRUE for both streams might not be right for you!
+nsresult NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result,
+ nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *target,
+ bool sourceBuffered = true,
+ bool sinkBuffered = true,
+ uint32_t chunkSize = 0,
+ bool closeSource = true,
+ bool closeSink = true);
+
+nsresult NS_NewLoadGroup(nsILoadGroup **result,
+ nsIRequestObserver *obs);
+
+// Create a new nsILoadGroup that will match the given principal.
+nsresult
+NS_NewLoadGroup(nsILoadGroup **aResult, nsIPrincipal* aPrincipal);
+
+// Determine if the given loadGroup/principal pair will produce a principal
+// with similar permissions when passed to NS_NewChannel(). This checks for
+// things like making sure the appId and browser element flags match. Without
+// an appropriate load group these values can be lost when getting the result
+// principal back out of the channel. Null principals are also always allowed
+// as they do not have permissions to actually use the load group.
+bool
+NS_LoadGroupMatchesPrincipal(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal);
+
+nsresult NS_NewDownloader(nsIStreamListener **result,
+ nsIDownloadObserver *observer,
+ nsIFile *downloadLocation = nullptr);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader **result,
+ nsIStreamLoaderObserver *observer,
+ nsIRequestObserver *requestObserver = nullptr);
+
+nsresult NS_NewIncrementalStreamLoader(nsIIncrementalStreamLoader **result,
+ nsIIncrementalStreamLoaderObserver *observer);
+
+nsresult NS_NewStreamLoaderInternal(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult /* NS_NewStreamLoaderNode */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsINode *aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult /* NS_NewStreamLoaderPrincipal */
+NS_NewStreamLoader(nsIStreamLoader **outStream,
+ nsIURI *aUri,
+ nsIStreamLoaderObserver *aObserver,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup = nullptr,
+ nsIInterfaceRequestor *aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIURI *aReferrer = nullptr);
+
+nsresult NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result,
+ nsIUnicharStreamLoaderObserver *observer);
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener **result,
+ nsIInputStream **stream);
+
+/**
+ * Implement the nsIChannel::Open(nsIInputStream**) method using the channel's
+ * AsyncOpen method.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+nsresult NS_ImplementChannelOpen(nsIChannel *channel,
+ nsIInputStream **result);
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver **result,
+ nsIRequestObserver *observer,
+ nsISupports *context);
+
+nsresult NS_NewSimpleStreamListener(nsIStreamListener **result,
+ nsIOutputStream *sink,
+ nsIRequestObserver *observer = nullptr);
+
+nsresult NS_CheckPortSafety(int32_t port,
+ const char *scheme,
+ nsIIOService *ioService = nullptr);
+
+// Determine if this URI is using a safe port.
+nsresult NS_CheckPortSafety(nsIURI *uri);
+
+nsresult NS_NewProxyInfo(const nsACString &type,
+ const nsACString &host,
+ int32_t port,
+ uint32_t flags,
+ nsIProxyInfo **result);
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler **result,
+ nsIIOService *ioService = nullptr);
+
+nsresult NS_GetFileFromURLSpec(const nsACString &inURL,
+ nsIFile **result,
+ nsIIOService *ioService = nullptr);
+
+nsresult NS_GetURLSpecFromFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are not directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromActualFile(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromDir(nsIFile *file,
+ nsACString &url,
+ nsIIOService *ioService = nullptr);
+
+/**
+ * Obtains the referrer for a given channel. This first tries to obtain the
+ * referrer from the property docshell.internalReferrer, and if that doesn't
+ * work and the channel is an nsIHTTPChannel, we check it's referrer property.
+ *
+ * @returns NS_ERROR_NOT_AVAILABLE if no referrer is available.
+ */
+nsresult NS_GetReferrerFromChannel(nsIChannel *channel,
+ nsIURI **referrer);
+
+nsresult NS_ParseRequestContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset);
+
+nsresult NS_ParseResponseContentType(const nsACString &rawContentType,
+ nsCString &contentType,
+ nsCString &contentCharset);
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString &rawContentType,
+ nsCString &contentCharset,
+ bool *hadCharset,
+ int32_t *charsetStart,
+ int32_t *charsetEnd);
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewPartialLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ uint64_t offset,
+ uint64_t length,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileStream(nsIFileStream **result,
+ nsIFile *file,
+ int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns the input end of a pipe. the output end of the pipe
+// is attached to the original stream. data from the original
+// stream is read into the pipe on a background thread.
+nsresult NS_BackgroundInputStream(nsIInputStream **result,
+ nsIInputStream *stream,
+ uint32_t segmentSize = 0,
+ uint32_t segmentCount = 0);
+
+// returns the output end of a pipe. the input end of the pipe
+// is attached to the original stream. data written to the pipe
+// is copied to the original stream on a background thread.
+nsresult NS_BackgroundOutputStream(nsIOutputStream **result,
+ nsIOutputStream *stream,
+ uint32_t segmentSize = 0,
+ uint32_t segmentCount = 0);
+
+MOZ_MUST_USE nsresult
+NS_NewBufferedInputStream(nsIInputStream **result,
+ nsIInputStream *str,
+ uint32_t bufferSize);
+
+// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the
+// provided stream supports it.
+nsresult NS_NewBufferedOutputStream(nsIOutputStream **result,
+ nsIOutputStream *str,
+ uint32_t bufferSize);
+
+/**
+ * Attempts to buffer a given stream. If this fails, it returns the
+ * passed-in stream.
+ *
+ * @param aOutputStream
+ * The output stream we want to buffer. This cannot be null.
+ * @param aBufferSize
+ * The size of the buffer for the buffered output stream.
+ * @returns an nsIOutputStream that is buffered with the specified buffer size,
+ * or is aOutputStream if creating the new buffered stream failed.
+ */
+already_AddRefed<nsIOutputStream>
+NS_BufferOutputStream(nsIOutputStream *aOutputStream,
+ uint32_t aBufferSize);
+already_AddRefed<nsIInputStream>
+NS_BufferInputStream(nsIInputStream *aInputStream,
+ uint32_t aBufferSize);
+
+// returns an input stream compatible with nsIUploadChannel::SetUploadStream()
+nsresult NS_NewPostDataStream(nsIInputStream **result,
+ bool isFile,
+ const nsACString &data);
+
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream,
+ void **aDest,
+ uint32_t aCount);
+
+// external code can't see fallible_t
+#ifdef MOZILLA_INTERNAL_API
+
+nsresult NS_ReadInputStreamToString(nsIInputStream *aInputStream,
+ nsACString &aDest,
+ uint32_t aCount);
+
+#endif
+
+nsresult
+NS_LoadPersistentPropertiesFromURISpec(nsIPersistentProperties **outResult,
+ const nsACString &aSpec);
+
+/**
+ * NS_QueryNotificationCallbacks implements the canonical algorithm for
+ * querying interfaces from a channel's notification callbacks. It first
+ * searches the channel's notificationCallbacks attribute, and if the interface
+ * is not found there, then it inspects the notificationCallbacks attribute of
+ * the channel's loadGroup.
+ *
+ * Note: templatized only because nsIWebSocketChannel is currently not an
+ * nsIChannel.
+ */
+template <class T> inline void
+NS_QueryNotificationCallbacks(T *channel,
+ const nsIID &iid,
+ void **result)
+{
+ NS_PRECONDITION(channel, "null channel");
+ *result = nullptr;
+
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ channel->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ channel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+// template helper:
+// Note: "class C" templatized only because nsIWebSocketChannel is currently not
+// an nsIChannel.
+
+template <class C, class T> inline void
+NS_QueryNotificationCallbacks(C *channel,
+ nsCOMPtr<T> &result)
+{
+ NS_QueryNotificationCallbacks(channel, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/**
+ * Alternate form of NS_QueryNotificationCallbacks designed for use by
+ * nsIChannel implementations.
+ */
+inline void
+NS_QueryNotificationCallbacks(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ const nsIID &iid,
+ void **result)
+{
+ *result = nullptr;
+
+ if (callbacks)
+ callbacks->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs)
+ cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+/**
+ * Returns true if channel is using Private Browsing, or false if not.
+ * Returns false if channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_UsePrivateBrowsing(nsIChannel *channel);
+
+/**
+ * Extract the NeckoOriginAttributes from the channel's triggering principal.
+ */
+bool NS_GetOriginAttributes(nsIChannel *aChannel,
+ mozilla::NeckoOriginAttributes &aAttributes);
+
+/**
+ * Returns true if the channel has visited any cross-origin URLs on any
+ * URLs that it was redirected through.
+ */
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false);
+
+// Constants duplicated from nsIScriptSecurityManager so we avoid having necko
+// know about script security manager.
+#define NECKO_NO_APP_ID 0
+#define NECKO_UNKNOWN_APP_ID UINT32_MAX
+// special app id reserved for separating the safebrowsing cookie
+#define NECKO_SAFEBROWSING_APP_ID UINT32_MAX - 1
+
+/**
+ * Gets AppId and isInIsolatedMozBrowserElement from channel's nsILoadContext.
+ * Returns false if error or channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_GetAppInfo(nsIChannel *aChannel,
+ uint32_t *aAppID,
+ bool *aIsInIsolatedMozBrowserElement);
+
+/**
+ * Gets appId and browserOnly parameters from the TOPIC_WEB_APP_CLEAR_DATA
+ * nsIObserverService notification. Used when clearing user data or
+ * uninstalling web apps.
+ */
+nsresult NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject,
+ uint32_t *aAppID,
+ bool *aBrowserOnly);
+
+/**
+ * Determines whether appcache should be checked for a given URI.
+ */
+bool NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing);
+
+bool NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing);
+
+/**
+ * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This
+ * method is provided mainly for use by other methods in this file.
+ *
+ * *aAuthPrompt2 should be set to null before calling this function.
+ */
+void NS_WrapAuthPrompt(nsIAuthPrompt *aAuthPrompt,
+ nsIAuthPrompt2 **aAuthPrompt2);
+
+/**
+ * Gets an auth prompt from an interface requestor. This takes care of wrapping
+ * an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2.
+ */
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor *aCallbacks,
+ nsIAuthPrompt2 **aAuthPrompt);
+
+/**
+ * Gets an nsIAuthPrompt2 from a channel. Use this instead of
+ * NS_QueryNotificationCallbacks for better backwards compatibility.
+ */
+void NS_QueryAuthPrompt2(nsIChannel *aChannel,
+ nsIAuthPrompt2 **aAuthPrompt);
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsCOMPtr<T> &result)
+{
+ NS_QueryNotificationCallbacks(callbacks, loadGroup,
+ NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(const nsCOMPtr<nsIInterfaceRequestor> &aCallbacks,
+ const nsCOMPtr<nsILoadGroup> &aLoadGroup,
+ nsCOMPtr<T> &aResult)
+{
+ NS_QueryNotificationCallbacks(aCallbacks.get(), aLoadGroup.get(), aResult);
+}
+
+/* template helper */
+template <class T> inline void
+NS_QueryNotificationCallbacks(const nsCOMPtr<nsIChannel> &aChannel,
+ nsCOMPtr<T> &aResult)
+{
+ NS_QueryNotificationCallbacks(aChannel.get(), aResult);
+}
+
+/**
+ * This function returns a nsIInterfaceRequestor instance that returns the
+ * same result as NS_QueryNotificationCallbacks when queried. It is useful
+ * as the value for nsISocketTransport::securityCallbacks.
+ */
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIEventTarget *target,
+ nsIInterfaceRequestor **result);
+
+nsresult
+NS_NewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks,
+ nsILoadGroup *loadGroup,
+ nsIInterfaceRequestor **result);
+
+/**
+ * Helper function for testing online/offline state of the browser.
+ */
+bool NS_IsOffline();
+
+/**
+ * Helper functions for implementing nsINestedURI::innermostURI.
+ *
+ * Note that NS_DoImplGetInnermostURI is "private" -- call
+ * NS_ImplGetInnermostURI instead.
+ */
+nsresult NS_DoImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result);
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI *nestedURI, nsIURI **result);
+
+/**
+ * Helper function that ensures that |result| is a URI that's safe to
+ * return. If |uri| is immutable, just returns it, otherwise returns
+ * a clone. |uri| must not be null.
+ */
+nsresult NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result);
+
+/**
+ * Helper function that tries to set the argument URI to be immutable
+ */
+void NS_TryToSetImmutable(nsIURI *uri);
+
+/**
+ * Helper function for calling ToImmutableURI. If all else fails, returns
+ * the input URI. The optional second arg indicates whether we had to fall
+ * back to the input URI. Passing in a null URI is ok.
+ */
+already_AddRefed<nsIURI> NS_TryToMakeImmutable(nsIURI *uri,
+ nsresult *outRv = nullptr);
+
+/**
+ * Helper function for testing whether the given URI, or any of its
+ * inner URIs, has all the given protocol flags.
+ */
+nsresult NS_URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result);
+
+/**
+ * Helper function for getting the innermost URI for a given URI. The return
+ * value could be just the object passed in if it's not a nested URI.
+ */
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI *aURI);
+
+/**
+ * Get the "final" URI for a channel. This is either the same as GetURI or
+ * GetOriginalURI, depending on whether this channel has
+ * nsIChanel::LOAD_REPLACE set. For channels without that flag set, the final
+ * URI is the original URI, while for ones with the flag the final URI is the
+ * channel URI.
+ */
+nsresult NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri);
+
+// NS_SecurityHashURI must return the same hash value for any two URIs that
+// compare equal according to NS_SecurityCompareURIs. Unfortunately, in the
+// case of files, it's not clear we can do anything better than returning
+// the schemeHash, so hashing files degenerates to storing them in a list.
+uint32_t NS_SecurityHashURI(nsIURI *aURI);
+
+bool NS_SecurityCompareURIs(nsIURI *aSourceURI,
+ nsIURI *aTargetURI,
+ bool aStrictFileOriginPolicy);
+
+bool NS_URIIsLocalFile(nsIURI *aURI);
+
+// When strict file origin policy is enabled, SecurityCompareURIs will fail for
+// file URIs that do not point to the same local file. This call provides an
+// alternate file-specific origin check that allows target files that are
+// contained in the same directory as the source.
+//
+// https://developer.mozilla.org/en-US/docs/Same-origin_policy_for_file:_URIs
+bool NS_RelaxStrictFileOriginPolicy(nsIURI *aTargetURI,
+ nsIURI *aSourceURI,
+ bool aAllowDirectoryTarget = false);
+
+bool NS_IsInternalSameURIRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags);
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags);
+
+nsresult NS_LinkRedirectChannels(uint32_t channelId,
+ nsIParentChannel *parentChannel,
+ nsIChannel **_result);
+
+/**
+ * Helper function to create a random URL string that's properly formed
+ * but guaranteed to be invalid.
+ */
+nsresult NS_MakeRandomInvalidURLString(nsCString &result);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using Open2() or has to fall back to opening
+ * the channel using Open().
+ */
+nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
+ nsIInputStream **aStream);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using AsyncOpen2() or has to fall back to opening
+ * the channel using AsyncOpen().
+ */
+nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel,
+ nsIStreamListener *aListener);
+
+/**
+ * Helper function to determine whether urlString is Java-compatible --
+ * whether it can be passed to the Java URL(String) constructor without the
+ * latter throwing a MalformedURLException, or without Java otherwise
+ * mishandling it. This function (in effect) implements a scheme whitelist
+ * for Java.
+ */
+nsresult NS_CheckIsJavaCompatibleURLString(nsCString& urlString, bool *result);
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString &aDispToken);
+
+/** Determine the disposition (inline/attachment) of the content based on the
+ * Content-Disposition header
+ * @param aHeader the content-disposition header (full value)
+ * @param aChan the channel the header came from
+ */
+uint32_t NS_GetContentDispositionFromHeader(const nsACString &aHeader,
+ nsIChannel *aChan = nullptr);
+
+/** Extracts the filename out of a content-disposition header
+ * @param aFilename [out] The filename. Can be empty on error.
+ * @param aDisposition Value of a Content-Disposition header
+ * @param aURI Optional. Will be used to get a fallback charset for the
+ * filename, if it is QI'able to nsIURL
+ */
+nsresult NS_GetFilenameFromDisposition(nsAString &aFilename,
+ const nsACString &aDisposition,
+ nsIURI *aURI = nullptr);
+
+/**
+ * Make sure Personal Security Manager is initialized
+ */
+void net_EnsurePSMInit();
+
+/**
+ * Test whether a URI is "about:blank". |uri| must not be null
+ */
+bool NS_IsAboutBlank(nsIURI *uri);
+
+nsresult NS_GenerateHostPort(const nsCString &host, int32_t port,
+ nsACString &hostLine);
+
+/**
+ * Sniff the content type for a given request or a given buffer.
+ *
+ * aSnifferType can be either NS_CONTENT_SNIFFER_CATEGORY or
+ * NS_DATA_SNIFFER_CATEGORY. The function returns the sniffed content type
+ * in the aSniffedType argument. This argument will not be modified if the
+ * content type could not be sniffed.
+ */
+void NS_SniffContent(const char *aSnifferType, nsIRequest *aRequest,
+ const uint8_t *aData, uint32_t aLength,
+ nsACString &aSniffedType);
+
+/**
+ * Whether the channel was created to load a srcdoc document.
+ * Note that view-source:about:srcdoc is classified as a srcdoc document by
+ * this function, which may not be applicable everywhere.
+ */
+bool NS_IsSrcdocChannel(nsIChannel *aChannel);
+
+/**
+ * Return true if the given string is a reasonable HTTP header value given the
+ * definition in RFC 2616 section 4.2. Currently we don't pay the cost to do
+ * full, sctrict validation here since it would require fulling parsing the
+ * value.
+ */
+bool NS_IsReasonableHTTPHeaderValue(const nsACString &aValue);
+
+/**
+ * Return true if the given string is a valid HTTP token per RFC 2616 section
+ * 2.2.
+ */
+bool NS_IsValidHTTPToken(const nsACString &aToken);
+
+/**
+ * Return true if the given request must be upgraded to HTTPS.
+ */
+nsresult NS_ShouldSecureUpgrade(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aChannelResultPrincipal,
+ bool aPrivateBrowsing,
+ bool aAllowSTS,
+ bool& aShouldUpgrade);
+
+/**
+ * Returns an https URI for channels that need to go through secure upgrades.
+ */
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI);
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel *aChannel);
+
+namespace mozilla {
+namespace net {
+
+const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
+const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL;
+const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL;
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(int64_t val);
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(uint64_t val);
+
+} // namespace net
+} // namespace mozilla
+
+// Include some function bodies for callers with external linkage
+#ifndef MOZILLA_INTERNAL_API
+#include "nsNetUtilInlines.h"
+#endif
+
+#endif // !nsNetUtil_h__
diff --git a/netwerk/base/nsNetUtilInlines.h b/netwerk/base/nsNetUtilInlines.h
new file mode 100644
index 000000000..7003814d5
--- /dev/null
+++ b/netwerk/base/nsNetUtilInlines.h
@@ -0,0 +1,395 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_inl
+#define nsNetUtil_inl
+
+#include "mozilla/Services.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIBufferedStreams.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIIOService.h"
+#include "nsINestedURI.h"
+#include "nsINode.h"
+#include "nsIProtocolHandler.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIURI.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsNetCID.h"
+#include "nsStringStream.h"
+
+#ifdef MOZILLA_INTERNAL_API
+// Don't allow functions that end up in nsNetUtil.cpp to be inlined out.
+#define INLINE_IF_EXTERN MOZ_NEVER_INLINE
+#else
+// Make sure that functions included via nsNetUtil.h don't get multiply defined.
+#define INLINE_IF_EXTERN MOZ_ALWAYS_INLINE
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+
+INLINE_IF_EXTERN already_AddRefed<nsIIOService>
+do_GetIOService(nsresult *error /* = 0 */)
+{
+ nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
+ if (error)
+ *error = io ? NS_OK : NS_ERROR_FAILURE;
+ return io.forget();
+}
+
+INLINE_IF_EXTERN already_AddRefed<nsINetUtil>
+do_GetNetUtil(nsresult *error /* = 0 */)
+{
+ nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
+ nsCOMPtr<nsINetUtil> util;
+ if (io)
+ util = do_QueryInterface(io);
+
+ if (error)
+ *error = !!util ? NS_OK : NS_ERROR_FAILURE;
+ return util.forget();
+}
+
+#else
+
+INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
+do_GetIOService(nsresult *error /* = 0 */)
+{
+ return nsGetServiceByContractIDWithError(NS_IOSERVICE_CONTRACTID, error);
+}
+
+INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
+do_GetNetUtil(nsresult *error /* = 0 */)
+{
+ return do_GetIOService(error);
+}
+#endif
+
+// private little helper function... don't call this directly!
+MOZ_ALWAYS_INLINE nsresult
+net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip)
+{
+ nsresult rv = NS_OK;
+ if (!*ios) {
+ grip = do_GetIOService(&rv);
+ *ios = grip;
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_URIChainHasFlags(nsIURI *uri,
+ uint32_t flags,
+ bool *result)
+{
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->URIChainHasFlags(uri, flags, result);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const nsACString &spec,
+ const char *charset /* = nullptr */,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService)
+ rv = ioService->NewURI(spec, charset, baseURI, result);
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const nsAString &spec,
+ const char *charset /* = nullptr */,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ return NS_NewURI(result, NS_ConvertUTF16toUTF8(spec), charset, baseURI, ioService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewURI(nsIURI **result,
+ const char *spec,
+ nsIURI *baseURI /* = nullptr */,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI, ioService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewFileURI(nsIURI **result,
+ nsIFile *spec,
+ nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService)
+ rv = ioService->NewFileURI(spec, result);
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsINode *aLoadingNode,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIPrincipal *aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURI2(
+ aUri,
+ aLoadingNode ?
+ aLoadingNode->AsDOMNode() : nullptr,
+ aLoadingPrincipal,
+ aTriggeringPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ // Retain the LOAD_REPLACE load flag if set.
+ nsLoadFlags normalLoadFlags = 0;
+ channel->GetLoadFlags(&normalLoadFlags);
+ rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewChannelInternal(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsILoadInfo *aLoadInfo,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ // NS_NewChannelInternal is mostly called for channel redirects. We should allow
+ // the creation of a channel even if the original channel did not have a loadinfo
+ // attached.
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithLoadInfo(
+ aUri,
+ aLoadInfo,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ // Retain the LOAD_REPLACE load flag if set.
+ nsLoadFlags normalLoadFlags = 0;
+ channel->GetLoadFlags(&normalLoadFlags);
+ rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN nsresult /* NS_NewChannelPrincipal */
+NS_NewChannel(nsIChannel **outChannel,
+ nsIURI *aUri,
+ nsIPrincipal *aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup *aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor *aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService *aIoService /* = nullptr */)
+{
+ return NS_NewChannelInternal(outChannel,
+ aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup,
+ aCallbacks,
+ aLoadFlags,
+ aIoService);
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewStreamLoader(nsIStreamLoader **result,
+ nsIStreamLoaderObserver *observer,
+ nsIRequestObserver *requestObserver /* = nullptr */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer, requestObserver);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewLocalFileInputStream(nsIInputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> in =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ in.forget(result);
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewLocalFileOutputStream(nsIOutputStream **result,
+ nsIFile *file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv))
+ out.forget(result);
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN MOZ_MUST_USE nsresult
+NS_NewBufferedInputStream(nsIInputStream **result,
+ nsIInputStream *str,
+ uint32_t bufferSize)
+{
+ nsresult rv;
+ nsCOMPtr<nsIBufferedInputStream> in =
+ do_CreateInstance(NS_BUFFEREDINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(str, bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ in.forget(result);
+ }
+ }
+ return rv;
+}
+
+INLINE_IF_EXTERN nsresult
+NS_NewPostDataStream(nsIInputStream **result,
+ bool isFile,
+ const nsACString &data)
+{
+ nsresult rv;
+
+ if (isFile) {
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIInputStream> fileStream;
+
+ rv = NS_NewNativeLocalFile(data, false, getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
+ if (NS_SUCCEEDED(rv)) {
+ // wrap the file stream with a buffered input stream
+ rv = NS_NewBufferedInputStream(result, fileStream, 8192);
+ }
+ }
+ return rv;
+ }
+
+ // otherwise, create a string stream for the data (copies)
+ nsCOMPtr<nsIStringInputStream> stream
+ (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = stream->SetData(data.BeginReading(), data.Length());
+ if (NS_FAILED(rv))
+ return rv;
+
+ stream.forget(result);
+ return NS_OK;
+}
+
+INLINE_IF_EXTERN bool
+NS_IsOffline()
+{
+ bool offline = true;
+ bool connectivity = true;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ ios->GetOffline(&offline);
+ ios->GetConnectivity(&connectivity);
+ }
+ return offline || !connectivity;
+}
+
+#endif // nsNetUtil_inl
diff --git a/netwerk/base/nsNetworkInfoService.cpp b/netwerk/base/nsNetworkInfoService.cpp
new file mode 100644
index 000000000..5b188c7f1
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <winsock2.h>
+#endif
+
+#include "nsNetworkInfoService.h"
+#include "mozilla/ScopeExit.h"
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+#include "NetworkInfoServiceImpl.h"
+#else
+#error "Unsupported platform for nsNetworkInfoService! Check moz.build"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsNetworkInfoService,
+ nsINetworkInfoService)
+
+nsNetworkInfoService::nsNetworkInfoService()
+{
+}
+
+nsresult
+nsNetworkInfoService::Init()
+{
+ return NS_OK;
+}
+
+nsresult
+nsNetworkInfoService::ListNetworkAddresses(nsIListNetworkAddressesListener* aListener)
+{
+ nsresult rv;
+
+ AddrMapType addrMap;
+ rv = DoListAddresses(addrMap);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+
+ uint32_t addrCount = addrMap.Count();
+ const char** addrStrings = (const char**) malloc(sizeof(*addrStrings) * addrCount);
+ if (!addrStrings) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+ auto autoFreeAddrStrings = MakeScopeExit([&] {
+ free(addrStrings);
+ });
+
+ uint32_t idx = 0;
+ for (auto iter = addrMap.Iter(); !iter.Done(); iter.Next()) {
+ addrStrings[idx++] = iter.Data().get();
+ }
+ aListener->OnListedNetworkAddresses(addrStrings, addrCount);
+ return NS_OK;
+}
+
+// TODO: Bug 1275373: https://bugzilla.mozilla.org/show_bug.cgi?id=1275373
+// Use platform-specific implementation of DoGetHostname on Cocoa and Windows.
+static nsresult
+DoGetHostname(nsACString& aHostname)
+{
+ char hostnameBuf[256];
+ int result = gethostname(hostnameBuf, 256);
+ if (result == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure that there is always a terminating NUL byte.
+ hostnameBuf[255] = '\0';
+
+ // Find the first '.', terminate string there.
+ char* dotLocation = strchr(hostnameBuf, '.');
+ if (dotLocation) {
+ *dotLocation = '\0';
+ }
+
+ if (strlen(hostnameBuf) == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aHostname.AssignASCII(hostnameBuf);
+ return NS_OK;
+}
+
+nsresult
+nsNetworkInfoService::GetHostname(nsIGetHostnameListener* aListener)
+{
+ nsresult rv;
+ nsCString hostnameStr;
+ rv = DoGetHostname(hostnameStr);
+ if (NS_FAILED(rv)) {
+ aListener->OnGetHostnameFailed();
+ return NS_OK;
+ }
+
+ aListener->OnGotHostname(hostnameStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetworkInfoService.h b/netwerk/base/nsNetworkInfoService.h
new file mode 100644
index 000000000..be6f686bd
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_nsNetworkInfoService_h
+#define mozilla_net_nsNetworkInfoService_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+
+#include "nsINetworkInfoService.h"
+
+#define NETWORKINFOSERVICE_CID \
+{ 0x296d0900, 0xf8ef, 0x4df0, \
+ { 0x9c, 0x35, 0xdb, 0x58, 0x62, 0xab, 0xc5, 0x8d } }
+
+namespace mozilla {
+namespace net {
+
+class nsNetworkInfoService final
+ : public nsINetworkInfoService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKINFOSERVICE
+
+ nsresult Init();
+
+ explicit nsNetworkInfoService();
+
+private:
+ virtual ~nsNetworkInfoService() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_dom_nsNetworkInfoService_h
diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp
new file mode 100644
index 000000000..37d3e8b6b
--- /dev/null
+++ b/netwerk/base/nsPACMan.cpp
@@ -0,0 +1,769 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPACMan.h"
+#include "nsThreadUtils.h"
+#include "nsIAuthPrompt.h"
+#include "nsIPromptFactory.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsNetUtil.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsISystemProxySettings.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gProxyLog("proxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+// The PAC thread does evaluations of both PAC files and
+// nsISystemProxySettings because they can both block the calling thread and we
+// don't want that on the main thread
+
+// Check to see if the underlying request was not an error page in the case of
+// a HTTP request. For other types of channels, just return true.
+static bool
+HttpRequestSucceeded(nsIStreamLoader *loader)
+{
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+
+ bool result = true; // default to assuming success
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ if (httpChannel)
+ httpChannel->GetRequestSucceeded(&result);
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+// The ExecuteCallback runnable is triggered by
+// nsPACManCallback::OnQueryComplete on the Main thread when its completion is
+// discovered on the pac thread
+
+class ExecuteCallback final : public Runnable
+{
+public:
+ ExecuteCallback(nsPACManCallback *aCallback,
+ nsresult status)
+ : mCallback(aCallback)
+ , mStatus(status)
+ {
+ }
+
+ void SetPACString(const nsCString &pacString)
+ {
+ mPACString = pacString;
+ }
+
+ void SetPACURL(const nsCString &pacURL)
+ {
+ mPACURL = pacURL;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACManCallback> mCallback;
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+};
+
+//-----------------------------------------------------------------------------
+
+// The PAC thread must be deleted from the main thread, this class
+// acts as a proxy to do that, as the PACMan is reference counted
+// and might be destroyed on either thread
+
+class ShutdownThread final : public Runnable
+{
+public:
+ explicit ShutdownThread(nsIThread *thread)
+ : mThread(thread)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+// Dispatch this to wait until the PAC thread shuts down.
+
+class WaitForThreadShutdown final : public Runnable
+{
+public:
+ explicit WaitForThreadShutdown(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mPACMan->mPACThread) {
+ mPACMan->mPACThread->Shutdown();
+ mPACMan->mPACThread = nullptr;
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// PACLoadComplete allows the PAC thread to tell the main thread that
+// the javascript PAC file has been installed (perhaps unsuccessfully)
+// and that there is no reason to queue executions anymore
+
+class PACLoadComplete final : public Runnable
+{
+public:
+ explicit PACLoadComplete(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACMan->mLoader = nullptr;
+ mPACMan->PostProcessPendingQ();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// ExecutePACThreadAction is used to proxy actions from the main
+// thread onto the PAC thread. There are 3 options: process the queue,
+// cancel the queue, and setup the javascript context with a new PAC file
+
+class ExecutePACThreadAction final : public Runnable
+{
+public:
+ // by default we just process the queue
+ explicit ExecutePACThreadAction(nsPACMan *aPACMan)
+ : mPACMan(aPACMan)
+ , mCancel(false)
+ , mCancelStatus(NS_OK)
+ , mSetupPAC(false)
+ { }
+
+ void CancelQueue (nsresult status)
+ {
+ mCancel = true;
+ mCancelStatus = status;
+ }
+
+ void SetupPAC (const char *text, uint32_t datalen, nsCString &pacURI)
+ {
+ mSetupPAC = true;
+ mSetupPACData.Assign(text, datalen);
+ mSetupPACURI = pacURI;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (mCancel) {
+ mPACMan->CancelPendingQ(mCancelStatus);
+ mCancel = false;
+ return NS_OK;
+ }
+
+ if (mSetupPAC) {
+ mSetupPAC = false;
+
+ mPACMan->mPAC.Init(mSetupPACURI,
+ mSetupPACData,
+ mPACMan->mIncludePath);
+
+ RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
+ NS_DispatchToMainThread(runnable);
+ return NS_OK;
+ }
+
+ mPACMan->ProcessPendingQ();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsPACMan> mPACMan;
+
+ bool mCancel;
+ nsresult mCancelStatus;
+
+ bool mSetupPAC;
+ nsCString mSetupPACData;
+ nsCString mSetupPACURI;
+};
+
+//-----------------------------------------------------------------------------
+
+PendingPACQuery::PendingPACQuery(nsPACMan *pacMan, nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse)
+ : mPACMan(pacMan)
+ , mCallback(callback)
+ , mOnMainThreadOnly(mainThreadResponse)
+{
+ uri->GetAsciiSpec(mSpec);
+ uri->GetAsciiHost(mHost);
+ uri->GetScheme(mScheme);
+ uri->GetPort(&mPort);
+}
+
+void
+PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
+{
+ if (!mCallback)
+ return;
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
+ runnable->SetPACString(pacString);
+ if (mOnMainThreadOnly)
+ NS_DispatchToMainThread(runnable);
+ else
+ runnable->Run();
+}
+
+void
+PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL)
+{
+ if (!mCallback)
+ return;
+
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
+ runnable->SetPACURL(pacURL);
+ if (mOnMainThreadOnly)
+ NS_DispatchToMainThread(runnable);
+ else
+ runnable->Run();
+}
+
+NS_IMETHODIMP
+PendingPACQuery::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ mPACMan->PostQuery(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool sThreadLocalSetup = false;
+static uint32_t sThreadLocalIndex = 0xdeadbeef; // out of range
+
+static const char *kPACIncludePath =
+ "network.proxy.autoconfig_url.include_path";
+
+nsPACMan::nsPACMan()
+ : mLoadPending(false)
+ , mShutdown(false)
+ , mLoadFailureCount(0)
+ , mInProgress(false)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
+ if (!sThreadLocalSetup){
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+ mPAC.SetThreadLocalIndex(sThreadLocalIndex);
+ mIncludePath = Preferences::GetBool(kPACIncludePath, false);
+}
+
+nsPACMan::~nsPACMan()
+{
+ if (mPACThread) {
+ if (NS_IsMainThread()) {
+ mPACThread->Shutdown();
+ }
+ else {
+ RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
+ NS_DispatchToMainThread(runnable);
+ }
+ }
+
+ NS_ASSERTION(mLoader == nullptr, "pac man not shutdown properly");
+ NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly");
+}
+
+void
+nsPACMan::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread");
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_ABORT);
+
+ RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this);
+ NS_DispatchToMainThread(runnable);
+}
+
+nsresult
+nsPACMan::AsyncGetProxyForURI(nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mShutdown)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Maybe Reload PAC
+ if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
+ TimeStamp::Now() > mScheduledReload) {
+ LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
+
+ LoadPACFromURI(EmptyCString());
+ }
+
+ RefPtr<PendingPACQuery> query =
+ new PendingPACQuery(this, uri, callback, mainThreadResponse);
+
+ if (IsPACURI(uri)) {
+ // deal with this directly instead of queueing it
+ query->Complete(NS_OK, EmptyCString());
+ return NS_OK;
+ }
+
+ return mPACThread->Dispatch(query, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult
+nsPACMan::PostQuery(PendingPACQuery *query)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mShutdown) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString());
+ return NS_OK;
+ }
+
+ // add a reference to the query while it is in the pending list
+ RefPtr<PendingPACQuery> addref(query);
+ mPendingQ.insertBack(addref.forget().take());
+ ProcessPendingQ();
+ return NS_OK;
+}
+
+nsresult
+nsPACMan::LoadPACFromURI(const nsCString &spec)
+{
+ NS_ENSURE_STATE(!mShutdown);
+ NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty());
+
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
+ NS_ENSURE_STATE(loader);
+
+ LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get()));
+ // Since we might get called from nsProtocolProxyService::Init, we need to
+ // post an event back to the main thread before we try to use the IO service.
+ //
+ // But, we need to flag ourselves as loading, so that we queue up any PAC
+ // queries the enter between now and when we actually load the PAC file.
+
+ if (!mLoadPending) {
+ nsresult rv;
+ if (NS_FAILED(rv = NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsPACMan::StartLoading))))
+ return rv;
+ mLoadPending = true;
+ }
+
+ CancelExistingLoad();
+
+ mLoader = loader;
+ if (!spec.IsEmpty()) {
+ mPACURISpec = spec;
+ mPACURIRedirectSpec.Truncate();
+ mNormalPACURISpec.Truncate(); // set at load time
+ mLoadFailureCount = 0; // reset
+ }
+
+ // reset to Null
+ mScheduledReload = TimeStamp();
+ return NS_OK;
+}
+
+void
+nsPACMan::StartLoading()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mLoadPending = false;
+
+ // CancelExistingLoad was called...
+ if (!mLoader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+
+ if (NS_SUCCEEDED(mLoader->Init(this, nullptr))) {
+ // Always hit the origin server when loading PAC.
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIURI> pacURI;
+ NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
+
+ // NOTE: This results in GetProxyForURI being called
+ if (pacURI) {
+ nsresult rv = pacURI->GetSpec(mNormalPACURISpec);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ NS_NewChannel(getter_AddRefs(channel),
+ pacURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL,
+ ios);
+ }
+ else {
+ LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n",
+ mPACURISpec.get()));
+ }
+
+ if (channel) {
+ channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
+ channel->SetNotificationCallbacks(this);
+ if (NS_SUCCEEDED(channel->AsyncOpen2(mLoader)))
+ return;
+ }
+ }
+ }
+
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_UNEXPECTED);
+}
+
+
+void
+nsPACMan::OnLoadFailure()
+{
+ int32_t minInterval = 5; // 5 seconds
+ int32_t maxInterval = 300; // 5 minutes
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
+ &minInterval);
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
+ &maxInterval);
+ }
+
+ int32_t interval = minInterval << mLoadFailureCount++; // seconds
+ if (!interval || interval > maxInterval)
+ interval = maxInterval;
+
+ mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval);
+
+ LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n",
+ interval, mLoadFailureCount));
+
+ // while we wait for the retry queued members should try direct
+ // even if that means fast failure.
+ PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE);
+}
+
+void
+nsPACMan::CancelExistingLoad()
+{
+ if (mLoader) {
+ nsCOMPtr<nsIRequest> request;
+ mLoader->GetRequest(getter_AddRefs(request));
+ if (request)
+ request->Cancel(NS_ERROR_ABORT);
+ mLoader = nullptr;
+ }
+}
+
+void
+nsPACMan::PostProcessPendingQ()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void
+nsPACMan::PostCancelPendingQ(nsresult status)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ pending->CancelQueue(status);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void
+nsPACMan::CancelPendingQ(nsresult status)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ RefPtr<PendingPACQuery> query;
+
+ while (!mPendingQ.isEmpty()) {
+ query = dont_AddRef(mPendingQ.popLast());
+ query->Complete(status, EmptyCString());
+ }
+
+ if (mShutdown)
+ mPAC.Shutdown();
+}
+
+void
+nsPACMan::ProcessPendingQ()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ while (ProcessPending());
+
+ if (mShutdown) {
+ mPAC.Shutdown();
+ } else {
+ // do GC while the thread has nothing pending
+ mPAC.GC();
+ }
+}
+
+// returns true if progress was made by shortening the queue
+bool
+nsPACMan::ProcessPending()
+{
+ if (mPendingQ.isEmpty())
+ return false;
+
+ // queue during normal load, but if we are retrying a failed load then
+ // fast fail the queries
+ if (mInProgress || (IsLoading() && !mLoadFailureCount))
+ return false;
+
+ RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst()));
+
+ if (mShutdown || IsLoading()) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString());
+ return true;
+ }
+
+ nsAutoCString pacString;
+ bool completed = false;
+ mInProgress = true;
+ nsAutoCString PACURI;
+
+ // first we need to consider the system proxy changing the pac url
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty() &&
+ !PACURI.Equals(mPACURISpec)) {
+ query->UseAlternatePACFile(PACURI);
+ LOG(("Use PAC from system settings: %s\n", PACURI.get()));
+ completed = true;
+ }
+
+ // now try the system proxy settings for this particular url if
+ // PAC was not specified
+ if (!completed && mSystemProxySettings && PACURI.IsEmpty() &&
+ NS_SUCCEEDED(mSystemProxySettings->
+ GetProxyForURI(query->mSpec, query->mScheme,
+ query->mHost, query->mPort,
+ pacString))) {
+ LOG(("Use proxy from system settings: %s\n", pacString.get()));
+ query->Complete(NS_OK, pacString);
+ completed = true;
+ }
+
+ // the systemproxysettings didn't complete the resolution. try via PAC
+ if (!completed) {
+ nsresult status = mPAC.GetProxyForURI(query->mSpec, query->mHost,
+ pacString);
+ LOG(("Use proxy from PAC: %s\n", pacString.get()));
+ query->Complete(status, pacString);
+ }
+
+ mInProgress = false;
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver,
+ nsIInterfaceRequestor, nsIChannelEventSink)
+
+NS_IMETHODIMP
+nsPACMan::OnStreamComplete(nsIStreamLoader *loader,
+ nsISupports *context,
+ nsresult status,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mLoader != loader) {
+ // If this happens, then it means that LoadPACFromURI was called more
+ // than once before the initial call completed. In this case, status
+ // should be NS_ERROR_ABORT, and if so, then we know that we can and
+ // should delay any processing.
+ LOG(("OnStreamComplete: called more than once\n"));
+ if (status == NS_ERROR_ABORT)
+ return NS_OK;
+ }
+
+ LOG(("OnStreamComplete: entry\n"));
+
+ if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) {
+ // Get the URI spec used to load this PAC script.
+ nsAutoCString pacURI;
+ {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri)
+ uri->GetAsciiSpec(pacURI);
+ }
+ }
+
+ // We assume that the PAC text is ASCII (or ISO-Latin-1). We've had this
+ // assumption forever, and some real-world PAC scripts actually have some
+ // non-ASCII text in comment blocks (see bug 296163).
+ const char *text = (const char *) data;
+
+ // we have succeeded in loading the pac file using a bunch of interfaces that
+ // are main thread only, unfortunately we have to initialize the instance of
+ // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the pac thread, because
+ // that is where it will be used.
+
+ RefPtr<ExecutePACThreadAction> pending =
+ new ExecutePACThreadAction(this);
+ pending->SetupPAC(text, dataLen, pacURI);
+ if (mPACThread)
+ mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL);
+
+ LOG(("OnStreamComplete: process the PAC contents\n"));
+
+ // Even if the PAC file could not be parsed, we did succeed in loading the
+ // data for it.
+ mLoadFailureCount = 0;
+ } else {
+ // We were unable to load the PAC file (presumably because of a network
+ // failure). Try again a little later.
+ LOG(("OnStreamComplete: unable to load PAC, retry later\n"));
+ OnLoadFailure();
+ }
+
+ if (NS_SUCCEEDED(status))
+ PostProcessPendingQ();
+ else
+ PostCancelPendingQ(status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPACMan::GetInterface(const nsIID &iid, void **result)
+{
+ // In case loading the PAC file requires authentication.
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIPromptFactory> promptFac = do_GetService("@mozilla.org/prompter;1");
+ NS_ENSURE_TRUE(promptFac, NS_ERROR_FAILURE);
+ return promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result));
+ }
+
+ // In case loading the PAC file results in a redirect.
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink *>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsPACMan::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> pacURI;
+ if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI)))))
+ return rv;
+
+ rv = pacURI->GetSpec(mPACURIRedirectSpec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ LOG(("nsPACMan redirect from original %s to redirected %s\n",
+ mPACURISpec.get(), mPACURIRedirectSpec.get()));
+
+ // do not update mPACURISpec - that needs to stay as the
+ // configured URI so that we can determine when the config changes.
+ // However do track the most recent URI in the redirect change
+ // as mPACURIRedirectSpec so that URI can be allowed to bypass
+ // the proxy and actually fetch the pac file.
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+void
+nsPACMan::NamePACThread()
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ PR_SetCurrentThreadName("Proxy Resolution");
+}
+
+nsresult
+nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
+{
+ mSystemProxySettings = systemProxySettings;
+
+ nsresult rv = NS_NewThread(getter_AddRefs(mPACThread), nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // don't check return value as it is not a big deal for this to fail.
+ mPACThread->Dispatch(NewRunnableMethod(this, &nsPACMan::NamePACThread),
+ nsIEventTarget::DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h
new file mode 100644
index 000000000..def0843cb
--- /dev/null
+++ b/netwerk/base/nsPACMan.h
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPACMan_h__
+#define nsPACMan_h__
+
+#include "nsIStreamLoader.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "ProxyAutoConfig.h"
+#include "nsThreadUtils.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "nsAutoPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Atomics.h"
+
+class nsISystemProxySettings;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class nsPACMan;
+class WaitForThreadShutdown;
+
+/**
+ * This class defines a callback interface used by AsyncGetProxyForURI.
+ */
+class NS_NO_VTABLE nsPACManCallback : public nsISupports
+{
+public:
+ /**
+ * This method is invoked on the same thread that called AsyncGetProxyForURI.
+ *
+ * @param status
+ * This parameter indicates whether or not the PAC query succeeded.
+ * @param pacString
+ * This parameter holds the value of the PAC string. It is empty when
+ * status is a failure code.
+ * @param newPACURL
+ * This parameter holds the URL of a new PAC file that should be loaded
+ * before the query is evaluated again. At least one of pacString and
+ * newPACURL should be 0 length.
+ */
+ virtual void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) = 0;
+};
+
+class PendingPACQuery final : public Runnable,
+ public LinkedListElement<PendingPACQuery>
+{
+public:
+ PendingPACQuery(nsPACMan *pacMan, nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mainThreadResponse);
+
+ // can be called from either thread
+ void Complete(nsresult status, const nsCString &pacString);
+ void UseAlternatePACFile(const nsCString &pacURL);
+
+ nsCString mSpec;
+ nsCString mScheme;
+ nsCString mHost;
+ int32_t mPort;
+
+ NS_IMETHOD Run(void); /* Runnable */
+
+private:
+ nsPACMan *mPACMan; // weak reference
+
+private:
+ RefPtr<nsPACManCallback> mCallback;
+ bool mOnMainThreadOnly;
+};
+
+/**
+ * This class provides an abstraction layer above the PAC thread. The methods
+ * defined on this class are intended to be called on the main thread only.
+ */
+
+class nsPACMan final : public nsIStreamLoaderObserver
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsPACMan();
+
+ /**
+ * This method may be called to shutdown the PAC manager. Any async queries
+ * that have not yet completed will either finish normally or be canceled by
+ * the time this method returns.
+ */
+ void Shutdown();
+
+ /**
+ * This method queries a PAC result asynchronously. The callback runs on the
+ * calling thread. If the PAC file has not yet been loaded, then this method
+ * will queue up the request, and complete it once the PAC file has been
+ * loaded.
+ *
+ * @param uri
+ * The URI to query.
+ * @param callback
+ * The callback to run once the PAC result is available.
+ * @param mustCallbackOnMainThread
+ * If set to false the callback can be made from the PAC thread
+ */
+ nsresult AsyncGetProxyForURI(nsIURI *uri,
+ nsPACManCallback *callback,
+ bool mustCallbackOnMainThread);
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param pacSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ */
+ nsresult LoadPACFromURI(const nsCString &pacSpec);
+
+ /**
+ * Returns true if we are currently loading the PAC file.
+ */
+ bool IsLoading() { return mLoader != nullptr; }
+
+ /**
+ * Returns true if the given URI matches the URI of our PAC file or the
+ * URI it has been redirected to. In the case of a chain of redirections
+ * only the current one being followed and the original are considered
+ * becuase this information is used, respectively, to determine if we
+ * should bypass the proxy (to fetch the pac file) or if the pac
+ * configuration has changed (and we should reload the pac file)
+ */
+ bool IsPACURI(const nsACString &spec)
+ {
+ return mPACURISpec.Equals(spec) || mPACURIRedirectSpec.Equals(spec) ||
+ mNormalPACURISpec.Equals(spec);
+ }
+
+ bool IsPACURI(nsIURI *uri) {
+ if (mPACURISpec.IsEmpty() && mPACURIRedirectSpec.IsEmpty()) {
+ return false;
+ }
+
+ nsAutoCString tmp;
+ nsresult rv = uri->GetSpec(tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return IsPACURI(tmp);
+ }
+
+ nsresult Init(nsISystemProxySettings *);
+ static nsPACMan *sInstance;
+
+ // PAC thread operations only
+ void ProcessPendingQ();
+ void CancelPendingQ(nsresult);
+
+private:
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ friend class PendingPACQuery;
+ friend class PACLoadComplete;
+ friend class ExecutePACThreadAction;
+ friend class WaitForThreadShutdown;
+
+ ~nsPACMan();
+
+ /**
+ * Cancel any existing load if any.
+ */
+ void CancelExistingLoad();
+
+ /**
+ * Start loading the PAC file.
+ */
+ void StartLoading();
+
+ /**
+ * Reload the PAC file if there is reason to.
+ */
+ void MaybeReloadPAC();
+
+ /**
+ * Called when we fail to load the PAC file.
+ */
+ void OnLoadFailure();
+
+ /**
+ * PostQuery() only runs on the PAC thread and it is used to
+ * place a pendingPACQuery into the queue and potentially
+ * execute the queue if it was otherwise empty
+ */
+ nsresult PostQuery(PendingPACQuery *query);
+
+ // PAC thread operations only
+ void PostProcessPendingQ();
+ void PostCancelPendingQ(nsresult);
+ bool ProcessPending();
+ void NamePACThread();
+
+private:
+ ProxyAutoConfig mPAC;
+ nsCOMPtr<nsIThread> mPACThread;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
+
+ // These specs are not nsIURI so that they can be used off the main thread.
+ // The non-normalized versions are directly from the configuration, the
+ // normalized version has been extracted from an nsIURI
+ nsCString mPACURISpec;
+ nsCString mPACURIRedirectSpec;
+ nsCString mNormalPACURISpec;
+
+ nsCOMPtr<nsIStreamLoader> mLoader;
+ bool mLoadPending;
+ Atomic<bool, Relaxed> mShutdown;
+ TimeStamp mScheduledReload;
+ uint32_t mLoadFailureCount;
+
+ bool mInProgress;
+ bool mIncludePath;
+};
+
+extern LazyLogModule gProxyLog;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsPACMan_h__
diff --git a/netwerk/base/nsPILoadGroupInternal.idl b/netwerk/base/nsPILoadGroupInternal.idl
new file mode 100644
index 000000000..6b12304a4
--- /dev/null
+++ b/netwerk/base/nsPILoadGroupInternal.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * Dumping ground for load group experimental work.
+ * This interface will never be frozen. If you are
+ * using any feature exposed by this interface, be aware that this interface
+ * will change and you will be broken. You have been warned.
+ */
+[scriptable, uuid(6ef2f8ac-9584-48f3-957a-0c94fff0c8c7)]
+interface nsPILoadGroupInternal : nsISupports
+{
+
+ /**
+ * Called when the load group has loaded main page and
+ * subresources. (i.e.essentially DOMComplete)
+ *
+ * @param aDefaultChanel
+ * The request channel for the base apge
+ */
+ void OnEndPageLoad(in nsIChannel aDefaultChannel);
+};
diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl
new file mode 100644
index 000000000..d49745fac
--- /dev/null
+++ b/netwerk/base/nsPISocketTransportService.idl
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISocketTransportService.idl"
+
+/**
+ * This is a private interface used by the internals of the networking library.
+ * It will never be frozen. Do not use it in external code.
+ */
+[scriptable, uuid(18f73bf1-b35b-4b7b-aa9a-11bcbdbc389c)]
+
+interface nsPISocketTransportService : nsIRoutedSocketTransportService
+{
+ /**
+ * init/shutdown routines.
+ */
+ void init();
+ void shutdown(in bool aXpcomShutdown);
+
+ /**
+ * controls the TCP sender window clamp
+ */
+ readonly attribute long sendBufferSize;
+
+ /**
+ * Controls whether the socket transport service is offline.
+ * Setting it offline will cause non-local socket detachment.
+ */
+ attribute boolean offline;
+
+ /**
+ * Controls the default timeout (in seconds) for sending keepalive probes.
+ */
+ readonly attribute long keepaliveIdleTime;
+
+ /**
+ * Controls the default interval (in seconds) between retrying keepalive probes.
+ */
+ readonly attribute long keepaliveRetryInterval;
+
+ /**
+ * Controls the default retransmission count for keepalive probes.
+ */
+ readonly attribute long keepaliveProbeCount;
+};
+
+%{C++
+/*
+ * Network activity indicator: we send out these topics no more than every
+ * blipIntervalMilliseconds (as set by the
+ * "network.activity.blipIntervalMilliseconds" preference: if 0 no notifications
+ * are sent) if the network is currently active (i.e. we're sending/receiving
+ * data to/from the socket).
+ */
+#define NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC "network-activity-blip-upload"
+#define NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC "network-activity-blip-download"
+
+%}
diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp
new file mode 100644
index 000000000..d203f5505
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPreloadedStream.h"
+#include "nsIRunnable.h"
+
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsPreloadedStream,
+ nsIInputStream,
+ nsIAsyncInputStream)
+
+nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream *aStream,
+ const char *data, uint32_t datalen)
+ : mStream(aStream),
+ mOffset(0),
+ mLen(datalen)
+{
+ mBuf = (char *) moz_xmalloc(datalen);
+ memcpy(mBuf, data, datalen);
+}
+
+nsPreloadedStream::~nsPreloadedStream()
+{
+ free(mBuf);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Close()
+{
+ mLen = 0;
+ return mStream->Close();
+}
+
+
+NS_IMETHODIMP
+nsPreloadedStream::Available(uint64_t *_retval)
+{
+ uint64_t avail = 0;
+
+ nsresult rv = mStream->Available(&avail);
+ if (NS_FAILED(rv))
+ return rv;
+ *_retval = avail + mLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Read(char *aBuf, uint32_t aCount,
+ uint32_t *_retval)
+{
+ if (!mLen)
+ return mStream->Read(aBuf, aCount, _retval);
+
+ uint32_t toRead = std::min(mLen, aCount);
+ memcpy(aBuf, mBuf + mOffset, toRead);
+ mOffset += toRead;
+ mLen -= toRead;
+ *_retval = toRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void *aClosure, uint32_t aCount,
+ uint32_t *result)
+{
+ if (!mLen)
+ return mStream->ReadSegments(aWriter, aClosure, aCount, result);
+
+ *result = 0;
+ while (mLen > 0 && aCount > 0) {
+ uint32_t toRead = std::min(mLen, aCount);
+ uint32_t didRead = 0;
+ nsresult rv;
+
+ rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ *result += didRead;
+ mOffset += didRead;
+ mLen -= didRead;
+ aCount -= didRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::IsNonBlocking(bool *_retval)
+{
+ return mStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::CloseWithStatus(nsresult aStatus)
+{
+ mLen = 0;
+ return mStream->CloseWithStatus(aStatus);
+}
+
+class RunOnThread : public Runnable
+{
+public:
+ RunOnThread(nsIAsyncInputStream *aStream,
+ nsIInputStreamCallback *aCallback)
+ : mStream(aStream),
+ mCallback(aCallback) {}
+
+ virtual ~RunOnThread() {}
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->OnInputStreamReady(mStream);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+};
+
+NS_IMETHODIMP
+nsPreloadedStream::AsyncWait(nsIInputStreamCallback *aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget *aEventTarget)
+{
+ if (!mLen)
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount,
+ aEventTarget);
+
+ if (!aCallback)
+ return NS_OK;
+
+ if (!aEventTarget)
+ return aCallback->OnInputStreamReady(this);
+
+ nsCOMPtr<nsIRunnable> event =
+ new RunOnThread(this, aCallback);
+ return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h
new file mode 100644
index 000000000..afdc960e7
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This class allows you to prefix an existing nsIAsyncInputStream
+ * with a preloaded block of data known at construction time by wrapping the
+ * two data sources into a new nsIAsyncInputStream. Readers of the new
+ * stream initially see the preloaded data and when that has been exhausted
+ * they automatically read from the wrapped stream.
+ *
+ * It is used by nsHttpConnection when it has over buffered while reading from
+ * the HTTP input socket and accidentally consumed data that belongs to
+ * a different protocol via the HTTP Upgrade mechanism. That over-buffered
+ * data is preloaded together with the input socket to form the new input socket
+ * given to the new protocol handler.
+*/
+
+#ifndef nsPreloadedStream_h__
+#define nsPreloadedStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+class nsPreloadedStream final : public nsIAsyncInputStream
+{
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ nsPreloadedStream(nsIAsyncInputStream *aStream,
+ const char *data, uint32_t datalen);
+private:
+ ~nsPreloadedStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ char *mBuf;
+ uint32_t mOffset;
+ uint32_t mLen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp
new file mode 100644
index 000000000..26eca0e88
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -0,0 +1,2146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+
+#include "nsProtocolProxyService.h"
+#include "nsProxyInfo.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIOService.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannel.h"
+#include "nsICancelable.h"
+#include "nsIDNSService.h"
+#include "nsPIDNSService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsThreadUtils.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "plstr.h"
+#include "prnetdb.h"
+#include "nsPACMan.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsISystemProxySettings.h"
+#include "nsINetworkLinkService.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Tokenizer.h"
+
+//----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+ extern const char kProxyType_HTTP[];
+ extern const char kProxyType_HTTPS[];
+ extern const char kProxyType_SOCKS[];
+ extern const char kProxyType_SOCKS4[];
+ extern const char kProxyType_SOCKS5[];
+ extern const char kProxyType_DIRECT[];
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+//----------------------------------------------------------------------------
+
+#define PROXY_PREF_BRANCH "network.proxy"
+#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x
+
+#define WPAD_URL "http://wpad/wpad.dat"
+
+//----------------------------------------------------------------------------
+
+// This structure is intended to be allocated on the stack
+struct nsProtocolInfo {
+ nsAutoCString scheme;
+ uint32_t flags;
+ int32_t defaultPort;
+};
+
+//----------------------------------------------------------------------------
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+static nsresult
+GetProxyURI(nsIChannel *channel, nsIURI **aOut)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> proxyURI;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI));
+ }
+ if (!proxyURI) {
+ rv = channel->GetURI(getter_AddRefs(proxyURI));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ proxyURI.forget(aOut);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+// The nsPACManCallback portion of this implementation should be run
+// on the main thread - so call nsPACMan::AsyncGetProxyForURI() with
+// a true mainThreadResponse parameter.
+class nsAsyncResolveRequest final : public nsIRunnable
+ , public nsPACManCallback
+ , public nsICancelable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncResolveRequest(nsProtocolProxyService *pps, nsIChannel *channel,
+ uint32_t aResolveFlags,
+ nsIProtocolProxyCallback *callback)
+ : mStatus(NS_OK)
+ , mDispatched(false)
+ , mResolveFlags(aResolveFlags)
+ , mPPS(pps)
+ , mXPComPPS(pps)
+ , mChannel(channel)
+ , mCallback(callback)
+ {
+ NS_ASSERTION(mCallback, "null callback");
+ }
+
+private:
+ ~nsAsyncResolveRequest()
+ {
+ if (!NS_IsMainThread()) {
+ // these xpcom pointers might need to be proxied back to the
+ // main thread to delete safely, but if this request had its
+ // callbacks called normally they will all be null and this is a nop
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread(mChannel.forget());
+ }
+
+ if (mCallback) {
+ NS_ReleaseOnMainThread(mCallback.forget());
+ }
+
+ if (mProxyInfo) {
+ NS_ReleaseOnMainThread(mProxyInfo.forget());
+ }
+
+ if (mXPComPPS) {
+ NS_ReleaseOnMainThread(mXPComPPS.forget());
+ }
+ }
+ }
+
+public:
+ void SetResult(nsresult status, nsIProxyInfo *pi)
+ {
+ mStatus = status;
+ mProxyInfo = pi;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mCallback)
+ DoCallback();
+ return NS_OK;
+ }
+
+ NS_IMETHOD Cancel(nsresult reason) override
+ {
+ NS_ENSURE_ARG(NS_FAILED(reason));
+
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback)
+ return NS_OK;
+
+ SetResult(reason, nullptr);
+ return DispatchCallback();
+ }
+
+ nsresult DispatchCallback()
+ {
+ if (mDispatched) // Only need to dispatch once
+ return NS_OK;
+
+ nsresult rv = NS_DispatchToCurrentThread(this);
+ if (NS_FAILED(rv))
+ NS_WARNING("unable to dispatch callback event");
+ else {
+ mDispatched = true;
+ return NS_OK;
+ }
+
+ mCallback = nullptr; // break possible reference cycle
+ return rv;
+ }
+
+private:
+
+ // Called asynchronously, so we do not need to post another PLEvent
+ // before calling DoCallback.
+ void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) override
+ {
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback)
+ return;
+
+ // Provided we haven't been canceled...
+ if (mStatus == NS_OK) {
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ }
+
+ // In the cancelation case, we may still have another PLEvent in
+ // the queue that wants to call DoCallback. No need to wait for
+ // it, just run the callback now.
+ DoCallback();
+ }
+
+ void DoCallback()
+ {
+ bool pacAvailable = true;
+ if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) {
+ // If the PAC service is not avail (e.g. failed pac load
+ // or shutdown) then we will be going direct. Make that
+ // mapping now so that any filters are still applied.
+ mPACString = NS_LITERAL_CSTRING("DIRECT;");
+ mStatus = NS_OK;
+
+ LOG(("pac not available, use DIRECT\n"));
+ pacAvailable = false;
+ }
+
+ // Generate proxy info from the PAC string if appropriate
+ if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) {
+ mPPS->ProcessPACString(mPACString, mResolveFlags,
+ getter_AddRefs(mProxyInfo));
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // Now apply proxy filters
+ nsProtocolInfo info;
+ mStatus = mPPS->GetProtocolInfo(proxyURI, &info);
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->ApplyFilters(mChannel, info, mProxyInfo);
+ else
+ mProxyInfo = nullptr;
+
+ if(pacAvailable) {
+ // if !pacAvailable, it was already logged above
+ LOG(("pac thread callback %s\n", mPACString.get()));
+ }
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+ else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) {
+ LOG(("pac thread callback indicates new pac file load\n"));
+
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // trigger load of new pac url
+ nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false);
+ if (NS_SUCCEEDED(rv)) {
+ // now that the load is triggered, we can resubmit the query
+ RefPtr<nsAsyncResolveRequest> newRequest =
+ new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags,
+ mCallback);
+ rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI,
+ newRequest,
+ true);
+ }
+
+ if (NS_FAILED(rv))
+ mCallback->OnProxyAvailable(this, mChannel, nullptr, rv);
+
+ // do not call onproxyavailable() in SUCCESS case - the newRequest will
+ // take care of that
+ }
+ else {
+ LOG(("pac thread callback did not provide information %X\n", mStatus));
+ if (NS_SUCCEEDED(mStatus))
+ mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+
+ // We are on the main thread now and don't need these any more so
+ // release them to avoid having to proxy them back to the main thread
+ // in the dtor
+ mCallback = nullptr; // in case the callback holds an owning ref to us
+ mPPS = nullptr;
+ mXPComPPS = nullptr;
+ mChannel = nullptr;
+ mProxyInfo = nullptr;
+ }
+
+private:
+
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mDispatched;
+ uint32_t mResolveFlags;
+
+ nsProtocolProxyService *mPPS;
+ nsCOMPtr<nsIProtocolProxyService> mXPComPPS;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIProtocolProxyCallback> mCallback;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+};
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable)
+
+//----------------------------------------------------------------------------
+
+#define IS_ASCII_SPACE(_c) ((_c) == ' ' || (_c) == '\t')
+
+//
+// apply mask to address (zeros out excluded bits).
+//
+// NOTE: we do the byte swapping here to minimize overall swapping.
+//
+static void
+proxy_MaskIPv6Addr(PRIPv6Addr &addr, uint16_t mask_len)
+{
+ if (mask_len == 128)
+ return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
+ }
+ else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
+ }
+ else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
+ }
+ else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
+ }
+}
+
+static void
+proxy_GetStringPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ nsCString &aResult)
+{
+ nsXPIDLCString temp;
+ nsresult rv = aPrefBranch->GetCharPref(aPref, getter_Copies(temp));
+ if (NS_FAILED(rv))
+ aResult.Truncate();
+ else {
+ aResult.Assign(temp);
+ // all of our string prefs are hostnames, so we should remove any
+ // whitespace characters that the user might have unknowingly entered.
+ aResult.StripWhitespace();
+ }
+}
+
+static void
+proxy_GetIntPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ int32_t &aResult)
+{
+ int32_t temp;
+ nsresult rv = aPrefBranch->GetIntPref(aPref, &temp);
+ if (NS_FAILED(rv))
+ aResult = -1;
+ else
+ aResult = temp;
+}
+
+static void
+proxy_GetBoolPref(nsIPrefBranch *aPrefBranch,
+ const char *aPref,
+ bool &aResult)
+{
+ bool temp;
+ nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp);
+ if (NS_FAILED(rv))
+ aResult = false;
+ else
+ aResult = temp;
+}
+
+//----------------------------------------------------------------------------
+
+static const int32_t PROXYCONFIG_DIRECT4X = 3;
+static const int32_t PROXYCONFIG_COUNT = 6;
+
+NS_IMPL_ADDREF(nsProtocolProxyService)
+NS_IMPL_RELEASE(nsProtocolProxyService)
+NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON,
+ NS_PROTOCOLPROXYSERVICE_CID)
+
+// NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change
+NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService)
+NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService)
+NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2)
+NS_INTERFACE_MAP_ENTRY(nsIObserver)
+if ( aIID.Equals(NS_GET_IID(nsProtocolProxyService)) ) foundInterface = static_cast<nsIProtocolProxyService2*>(this); else
+NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService)
+NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService,
+ nsIProtocolProxyService,
+ nsIProtocolProxyService2)
+
+nsProtocolProxyService::nsProtocolProxyService()
+ : mFilterLocalHosts(false)
+ , mFilters(nullptr)
+ , mProxyConfig(PROXYCONFIG_DIRECT)
+ , mHTTPProxyPort(-1)
+ , mFTPProxyPort(-1)
+ , mHTTPSProxyPort(-1)
+ , mSOCKSProxyPort(-1)
+ , mSOCKSProxyVersion(4)
+ , mSOCKSProxyRemoteDNS(false)
+ , mProxyOverTLS(true)
+ , mPACMan(nullptr)
+ , mSessionStart(PR_Now())
+ , mFailedProxyTimeout(30 * 60) // 30 minute default
+{
+}
+
+nsProtocolProxyService::~nsProtocolProxyService()
+{
+ // These should have been cleaned up in our Observe method.
+ NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr &&
+ mPACMan == nullptr, "what happened to xpcom-shutdown?");
+}
+
+// nsProtocolProxyService methods
+nsresult
+nsProtocolProxyService::Init()
+{
+ // failure to access prefs is non-fatal
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ // monitor proxy prefs
+ prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);
+
+ // read all prefs
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ // register for shutdown notification so we can clean ourselves up
+ // properly.
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ return NS_OK;
+}
+
+// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
+// to call ReloadPAC()
+nsresult
+nsProtocolProxyService::ReloadNetworkPAC()
+{
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return NS_OK;
+ }
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (type == PROXYCONFIG_PAC) {
+ nsXPIDLCString pacSpec;
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"),
+ getter_Copies(pacSpec));
+ if (!pacSpec.IsEmpty()) {
+ nsCOMPtr<nsIURI> pacURI;
+ rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec);
+ if(!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ nsProtocolInfo pac;
+ rv = GetProtocolInfo(pacURI, &pac);
+ if(!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (!pac.scheme.EqualsLiteral("file") &&
+ !pac.scheme.EqualsLiteral("data")) {
+ LOG((": received network changed event, reload PAC"));
+ ReloadPAC();
+ }
+ }
+ } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) {
+ ReloadPAC();
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsProtocolProxyService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // cleanup
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+ if (mFilters) {
+ delete mFilters;
+ mFilters = nullptr;
+ }
+ if (mPACMan) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char *state = converted.get();
+ if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) {
+ ReloadNetworkPAC();
+ }
+ }
+ else {
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "what is this random observer event?");
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ if (prefs)
+ PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get());
+ }
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch,
+ const char *pref)
+{
+ nsresult rv = NS_OK;
+ bool reloadPAC = false;
+ nsXPIDLCString tempString;
+
+ if (!pref || !strcmp(pref, PROXY_PREF("type"))) {
+ int32_t type = -1;
+ rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_SUCCEEDED(rv)) {
+ // bug 115720 - for ns4.x backwards compatibility
+ if (type == PROXYCONFIG_DIRECT4X) {
+ type = PROXYCONFIG_DIRECT;
+ // Reset the type so that the dialog looks correct, and we
+ // don't have to handle this case everywhere else
+ // I'm paranoid about a loop of some sort - only do this
+ // if we're enumerating all prefs, and ignore any error
+ if (!pref)
+ prefBranch->SetIntPref(PROXY_PREF("type"), type);
+ } else if (type >= PROXYCONFIG_COUNT) {
+ LOG(("unknown proxy type: %lu; assuming direct\n", type));
+ type = PROXYCONFIG_DIRECT;
+ }
+ mProxyConfig = type;
+ reloadPAC = true;
+ }
+
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (!mSystemProxySettings)
+ mProxyConfig = PROXYCONFIG_DIRECT;
+ ResetPACThread();
+ } else {
+ if (mSystemProxySettings) {
+ mSystemProxySettings = nullptr;
+ ResetPACThread();
+ }
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ftp")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ftp"), mFTPProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ftp_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ftp_port"), mFTPProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) {
+ int32_t version;
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version);
+ // make sure this preference value remains sane
+ if (version == 5)
+ mSOCKSProxyVersion = 5;
+ else
+ mSOCKSProxyVersion = 4;
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns")))
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
+ mSOCKSProxyRemoteDNS);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"),
+ mProxyOverTLS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
+ mFailedProxyTimeout);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
+ rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"),
+ getter_Copies(tempString));
+ if (NS_SUCCEEDED(rv))
+ LoadHostFilters(tempString);
+ }
+
+ // We're done if not using something that could give us a PAC URL
+ // (PAC, WPAD or System)
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM)
+ return;
+
+ // OK, we need to reload the PAC file if:
+ // 1) network.proxy.type changed, or
+ // 2) network.proxy.autoconfig_url changed and PAC is configured
+
+ if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url")))
+ reloadPAC = true;
+
+ if (reloadPAC) {
+ tempString.Truncate();
+ if (mProxyConfig == PROXYCONFIG_PAC) {
+ prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"),
+ getter_Copies(tempString));
+ if (mPACMan && !mPACMan->IsPACURI(tempString)) {
+ LOG(("PAC Thread URI Changed - Reset Pac Thread"));
+ ResetPACThread();
+ }
+ } else if (mProxyConfig == PROXYCONFIG_WPAD) {
+ // We diverge from the WPAD spec here in that we don't walk the
+ // hosts's FQDN, stripping components until we hit a TLD. Doing so
+ // is dangerous in the face of an incomplete list of TLDs, and TLDs
+ // get added over time. We could consider doing only a single
+ // substitution of the first component, if that proves to help
+ // compatibility.
+ tempString.AssignLiteral(WPAD_URL);
+ } else if (mSystemProxySettings) {
+ // Get System Proxy settings if available
+ mSystemProxySettings->GetPACURI(tempString);
+ }
+ if (!tempString.IsEmpty())
+ ConfigureFromPAC(tempString, false);
+ }
+}
+
+bool
+nsProtocolProxyService::CanUseProxy(nsIURI *aURI, int32_t defaultPort)
+{
+ if (mHostFiltersArray.Length() == 0)
+ return true;
+
+ int32_t port;
+ nsAutoCString host;
+
+ nsresult rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty())
+ return false;
+
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv))
+ return false;
+ if (port == -1)
+ port = defaultPort;
+
+ PRNetAddr addr;
+ bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ PRIPv6Addr ipv6;
+ if (is_ipaddr) {
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6);
+ }
+ else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ }
+ else {
+ NS_WARNING("unknown address family");
+ return true; // allow proxying
+ }
+ }
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) ||
+ host.EqualsLiteral("127.0.0.1") ||
+ host.EqualsLiteral("::1")) {
+ LOG(("Not using proxy for this local host [%s]!\n", host.get()));
+ return false; // don't allow proxying
+ }
+
+ int32_t index = -1;
+ while (++index < int32_t(mHostFiltersArray.Length())) {
+ HostInfo *hinfo = mHostFiltersArray[index];
+
+ if (is_ipaddr != hinfo->is_ipaddr)
+ continue;
+ if (hinfo->port && hinfo->port != port)
+ continue;
+
+ if (is_ipaddr) {
+ // generate masked version of target IPv6 address
+ PRIPv6Addr masked;
+ memcpy(&masked, &ipv6, sizeof(PRIPv6Addr));
+ proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len);
+
+ // check for a match
+ if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0)
+ return false; // proxy disallowed
+ }
+ else {
+ uint32_t host_len = host.Length();
+ uint32_t filter_host_len = hinfo->name.host_len;
+
+ if (host_len >= filter_host_len) {
+ //
+ // compare last |filter_host_len| bytes of target hostname.
+ //
+ const char *host_tail = host.get() + host_len - filter_host_len;
+ if (!PL_strncasecmp(host_tail, hinfo->name.host, filter_host_len)) {
+ // If the tail of the host string matches the filter
+
+ if (filter_host_len > 0 && hinfo->name.host[0] == '.') {
+ // If the filter was of the form .foo.bar.tld, all such
+ // matches are correct
+ return false; // proxy disallowed
+ }
+
+ // abc-def.example.org should not match def.example.org
+ // however, *.def.example.org should match .def.example.org
+ // We check that the filter doesn't start with a `.`. If it does,
+ // then the strncasecmp above should suffice. If it doesn't,
+ // then we should only consider it a match if the strncasecmp happened
+ // at a subdomain boundary
+ if (host_len > filter_host_len && *(host_tail - 1) == '.') {
+ // If the host was something.foo.bar.tld and the filter
+ // was foo.bar.tld, it's still a match.
+ // the character right before the tail must be a
+ // `.` for this to work
+ return false; // proxy disallowed
+ }
+
+ if (host_len == filter_host_len) {
+ // If the host and filter are of the same length,
+ // they should match
+ return false; // proxy disallowed
+ }
+ }
+
+ }
+ }
+ }
+ return true;
+}
+
+// kProxyType\* may be referred to externally in
+// nsProxyInfo in order to compare by string pointer
+const char kProxyType_HTTP[] = "http";
+const char kProxyType_HTTPS[] = "https";
+const char kProxyType_PROXY[] = "proxy";
+const char kProxyType_SOCKS[] = "socks";
+const char kProxyType_SOCKS4[] = "socks4";
+const char kProxyType_SOCKS5[] = "socks5";
+const char kProxyType_DIRECT[] = "direct";
+
+const char *
+nsProtocolProxyService::ExtractProxyInfo(const char *start,
+ uint32_t aResolveFlags,
+ nsProxyInfo **result)
+{
+ *result = nullptr;
+ uint32_t flags = 0;
+
+ // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl
+
+ // find end of proxy info delimiter
+ const char *end = start;
+ while (*end && *end != ';') ++end;
+
+ // find end of proxy type delimiter
+ const char *sp = start;
+ while (sp < end && *sp != ' ' && *sp != '\t') ++sp;
+
+ uint32_t len = sp - start;
+ const char *type = nullptr;
+ switch (len) {
+ case 4:
+ if (PL_strncasecmp(start, kProxyType_HTTP, 5) == 0) {
+ type = kProxyType_HTTP;
+ }
+ break;
+ case 5:
+ if (PL_strncasecmp(start, kProxyType_PROXY, 5) == 0) {
+ type = kProxyType_HTTP;
+ } else if (PL_strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
+ type = kProxyType_SOCKS4; // assume v4 for 4x compat
+ } else if (PL_strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
+ type = kProxyType_HTTPS;
+ }
+ break;
+ case 6:
+ if (PL_strncasecmp(start, kProxyType_DIRECT, 6) == 0)
+ type = kProxyType_DIRECT;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS4, 6) == 0)
+ type = kProxyType_SOCKS4;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS5, 6) == 0)
+ // map "SOCKS5" to "socks" to match contract-id of registered
+ // SOCKS-v5 socket provider.
+ type = kProxyType_SOCKS;
+ break;
+ }
+ if (type) {
+ const char *host = nullptr, *hostEnd = nullptr;
+ int32_t port = -1;
+
+ // If it's a SOCKS5 proxy, do name resolution on the server side.
+ // We could use this with SOCKS4a servers too, but they might not
+ // support it.
+ if (type == kProxyType_SOCKS || mSOCKSProxyRemoteDNS)
+ flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+
+ // extract host:port
+ start = sp;
+ while ((*start == ' ' || *start == '\t') && start < end)
+ start++;
+
+ // port defaults
+ if (type == kProxyType_HTTP) {
+ port = 80;
+ } else if (type == kProxyType_HTTPS) {
+ port = 443;
+ } else {
+ port = 1080;
+ }
+
+ nsProxyInfo *pi = new nsProxyInfo();
+ pi->mType = type;
+ pi->mFlags = flags;
+ pi->mResolveFlags = aResolveFlags;
+ pi->mTimeout = mFailedProxyTimeout;
+
+ // www.foo.com:8080 and http://www.foo.com:8080
+ nsDependentCSubstring maybeURL(start, end - start);
+ nsCOMPtr<nsIURI> pacURI;
+
+ nsAutoCString urlHost;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) &&
+ NS_SUCCEEDED(pacURI->GetAsciiHost(urlHost)) &&
+ !urlHost.IsEmpty()) {
+ // http://www.example.com:8080
+
+ pi->mHost = urlHost;
+
+ int32_t tPort;
+ if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) {
+ port = tPort;
+ }
+ pi->mPort = port;
+ }
+ else {
+ // www.example.com:8080
+ if (start < end) {
+ host = start;
+ hostEnd = strchr(host, ':');
+ if (!hostEnd || hostEnd > end) {
+ hostEnd = end;
+ // no port, so assume default
+ }
+ else {
+ port = atoi(hostEnd + 1);
+ }
+ }
+ // YES, it is ok to specify a null proxy host.
+ if (host) {
+ pi->mHost.Assign(host, hostEnd - host);
+ pi->mPort = port;
+ }
+ }
+ NS_ADDREF(*result = pi);
+ }
+
+ while (*end == ';' || *end == ' ' || *end == '\t')
+ ++end;
+ return end;
+}
+
+void
+nsProtocolProxyService::GetProxyKey(nsProxyInfo *pi, nsCString &key)
+{
+ key.AssignASCII(pi->mType);
+ if (!pi->mHost.IsEmpty()) {
+ key.Append(' ');
+ key.Append(pi->mHost);
+ key.Append(':');
+ key.AppendInt(pi->mPort);
+ }
+}
+
+uint32_t
+nsProtocolProxyService::SecondsSinceSessionStart()
+{
+ PRTime now = PR_Now();
+
+ // get time elapsed since session start
+ int64_t diff = now - mSessionStart;
+
+ // convert microseconds to seconds
+ diff /= PR_USEC_PER_SEC;
+
+ // return converted 32 bit value
+ return uint32_t(diff);
+}
+
+void
+nsProtocolProxyService::EnableProxy(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+ mFailedProxies.Remove(key);
+}
+
+void
+nsProtocolProxyService::DisableProxy(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // Add timeout to interval (this is the time when the proxy can
+ // be tried again).
+ dsec += pi->mTimeout;
+
+ // NOTE: The classic codebase would increase the timeout value
+ // incrementally each time a subsequent failure occurred.
+ // We could do the same, but it would require that we not
+ // remove proxy entries in IsProxyDisabled or otherwise
+ // change the way we are recording disabled proxies.
+ // Simpler is probably better for now, and at least the
+ // user can tune the timeout setting via preferences.
+
+ LOG(("DisableProxy %s %d\n", key.get(), dsec));
+
+ // If this fails, oh well... means we don't have enough memory
+ // to remember the failed proxy.
+ mFailedProxies.Put(key, dsec);
+}
+
+bool
+nsProtocolProxyService::IsProxyDisabled(nsProxyInfo *pi)
+{
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t val;
+ if (!mFailedProxies.Get(key, &val))
+ return false;
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // if time passed has exceeded interval, then try proxy again.
+ if (dsec > val) {
+ mFailedProxies.Remove(key);
+ return false;
+ }
+
+ return true;
+}
+
+nsresult
+nsProtocolProxyService::SetupPACThread()
+{
+ if (mPACMan)
+ return NS_OK;
+
+ mPACMan = new nsPACMan();
+
+ bool mainThreadOnly;
+ nsresult rv;
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ rv = mPACMan->Init(mSystemProxySettings);
+ }
+ else {
+ rv = mPACMan->Init(nullptr);
+ }
+
+ if (NS_FAILED(rv))
+ mPACMan = nullptr;
+ return rv;
+}
+
+nsresult
+nsProtocolProxyService::ResetPACThread()
+{
+ if (!mPACMan)
+ return NS_OK;
+
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ return SetupPACThread();
+}
+
+nsresult
+nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec,
+ bool forceReload)
+{
+ SetupPACThread();
+
+ if (mPACMan->IsPACURI(spec) && !forceReload)
+ return NS_OK;
+
+ mFailedProxies.Clear();
+
+ return mPACMan->LoadPACFromURI(spec);
+}
+
+void
+nsProtocolProxyService::ProcessPACString(const nsCString &pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result)
+{
+ if (pacString.IsEmpty()) {
+ *result = nullptr;
+ return;
+ }
+
+ const char *proxies = pacString.get();
+
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ while (*proxies) {
+ proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi);
+ if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) {
+ delete pi;
+ pi = nullptr;
+ }
+
+ if (pi) {
+ if (last) {
+ NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo");
+ last->mNext = pi;
+ }
+ else
+ first = pi;
+ last = pi;
+ }
+ }
+ *result = first;
+}
+
+// nsIProtocolProxyService2
+NS_IMETHODIMP
+nsProtocolProxyService::ReloadPAC()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return NS_OK;
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ nsXPIDLCString pacSpec;
+ if (type == PROXYCONFIG_PAC)
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(pacSpec));
+ else if (type == PROXYCONFIG_WPAD)
+ pacSpec.AssignLiteral(WPAD_URL);
+
+ if (!pacSpec.IsEmpty())
+ ConfigureFromPAC(pacSpec, true);
+ return NS_OK;
+}
+
+// When sync interface is removed this can go away too
+// The nsPACManCallback portion of this implementation should be run
+// off the main thread, because it uses a condvar for signaling and
+// the main thread is blocking on that condvar -
+// so call nsPACMan::AsyncGetProxyForURI() with
+// a false mainThreadResponse parameter.
+class nsAsyncBridgeRequest final : public nsPACManCallback
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncBridgeRequest()
+ : mMutex("nsDeprecatedCallback")
+ , mCondVar(mMutex, "nsDeprecatedCallback")
+ , mStatus(NS_OK)
+ , mCompleted(false)
+ {
+ }
+
+ void OnQueryComplete(nsresult status,
+ const nsCString &pacString,
+ const nsCString &newPACURL) override
+ {
+ MutexAutoLock lock(mMutex);
+ mCompleted = true;
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ mCondVar.Notify();
+ }
+
+ void Lock() { mMutex.Lock(); }
+ void Unlock() { mMutex.Unlock(); }
+ void Wait() { mCondVar.Wait(PR_SecondsToInterval(3)); }
+
+private:
+ ~nsAsyncBridgeRequest()
+ {
+ }
+
+ friend class nsProtocolProxyService;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mCompleted;
+};
+NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)
+
+// nsProtocolProxyService
+nsresult
+nsProtocolProxyService::DeprecatedBlockingResolve(nsIChannel *aChannel,
+ uint32_t aFlags,
+ nsIProxyInfo **retval)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(aChannel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv = Resolve_Internal(aChannel, info, aFlags,
+ &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!usePACThread || !mPACMan) {
+ ApplyFilters(aChannel, info, pi);
+ pi.forget(retval);
+ return NS_OK;
+ }
+
+ // Use the PAC thread to do the work, so we don't have to reimplement that
+ // code, but block this thread on that completion.
+ RefPtr<nsAsyncBridgeRequest> ctx = new nsAsyncBridgeRequest();
+ ctx->Lock();
+ if (NS_SUCCEEDED(mPACMan->AsyncGetProxyForURI(uri, ctx, false))) {
+ // this can really block the main thread, so cap it at 3 seconds
+ ctx->Wait();
+ }
+ ctx->Unlock();
+ if (!ctx->mCompleted)
+ return NS_ERROR_FAILURE;
+ if (NS_FAILED(ctx->mStatus))
+ return ctx->mStatus;
+
+ // pretty much duplicate real DoCallback logic
+
+ // Generate proxy info from the PAC string if appropriate
+ if (!ctx->mPACString.IsEmpty()) {
+ LOG(("sync pac thread callback %s\n", ctx->mPACString.get()));
+ ProcessPACString(ctx->mPACString, 0, getter_AddRefs(pi));
+ ApplyFilters(aChannel, info, pi);
+ pi.forget(retval);
+ return NS_OK;
+ }
+
+ if (!ctx->mPACURL.IsEmpty()) {
+ NS_WARNING("sync pac thread callback indicates new pac file load\n");
+ // This is a problem and is one of the reasons this blocking interface
+ // is deprecated. The main loop needs to spin to make this reload happen. So
+ // we are going to kick off the reload and return an error - it will work
+ // next time. Because this sync interface is only used in the java plugin it
+ // is extremely likely that the pac file has already been loaded anyhow.
+
+ rv = ConfigureFromPAC(ctx->mPACURL, false);
+ if (NS_FAILED(rv))
+ return rv;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *retval = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result,
+ bool isSyncOK)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+ NS_ENSURE_ARG_POINTER(callback);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ RefPtr<nsAsyncResolveRequest> ctx =
+ new nsAsyncResolveRequest(this, channel, flags, callback);
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv = Resolve_Internal(channel, info, flags,
+ &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!usePACThread || !mPACMan) {
+ // we can do it locally
+ ApplyFilters(channel, info, pi);
+ ctx->SetResult(NS_OK, pi);
+ if (isSyncOK) {
+ ctx->Run();
+ return NS_OK;
+ }
+
+ rv = ctx->DispatchCallback();
+ if (NS_SUCCEEDED(rv))
+ ctx.forget(result);
+ return rv;
+ }
+
+ // else kick off a PAC thread query
+
+ rv = mPACMan->AsyncGetProxyForURI(uri, ctx, true);
+ if (NS_SUCCEEDED(rv))
+ ctx.forget(result);
+ return rv;
+}
+
+// nsIProtocolProxyService
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve2(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result)
+{
+ return AsyncResolveInternal(channel, flags, callback, result, true);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve(nsISupports *channelOrURI, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result)
+{
+
+ nsresult rv;
+ // Check if we got a channel:
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelOrURI);
+ if (!channel) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(channelOrURI);
+ if (!uri) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // creating a temporary channel from the URI which is not
+ // used to perform any network loads, hence its safe to
+ // use systemPrincipal as the loadingPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ systemPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AsyncResolveInternal(channel, flags, callback, result, false);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfo(const nsACString &aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ nsIProxyInfo **aResult)
+{
+ return NewProxyInfoWithAuth(aType, aHost, aPort,
+ EmptyCString(), EmptyCString(),
+ aFlags, aFailoverTimeout,
+ aFailoverProxy, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfoWithAuth(const nsACString &aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ const nsACString &aUsername,
+ const nsACString &aPassword,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ nsIProxyInfo **aResult)
+{
+ static const char *types[] = {
+ kProxyType_HTTP,
+ kProxyType_HTTPS,
+ kProxyType_SOCKS,
+ kProxyType_SOCKS4,
+ kProxyType_DIRECT
+ };
+
+ // resolve type; this allows us to avoid copying the type string into each
+ // proxy info instance. we just reference the string literals directly :)
+ const char *type = nullptr;
+ for (uint32_t i = 0; i < ArrayLength(types); ++i) {
+ if (aType.LowerCaseEqualsASCII(types[i])) {
+ type = types[i];
+ break;
+ }
+ }
+ NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG);
+
+ // We have only implemented username/password for SOCKS proxies.
+ if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NewProxyInfo_Internal(type, aHost, aPort,
+ aUsername, aPassword,
+ aFlags, aFailoverTimeout,
+ aFailoverProxy, 0, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo *aProxy,
+ nsIURI *aURI,
+ nsresult aStatus,
+ nsIProxyInfo **aResult)
+{
+ // We only support failover when a PAC file is configured, either
+ // directly or via system settings
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Verify that |aProxy| is one of our nsProxyInfo objects.
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ NS_ENSURE_ARG(pi);
+ // OK, the QI checked out. We can proceed.
+
+ // Remember that this proxy is down.
+ DisableProxy(pi);
+
+ // NOTE: At this point, we might want to prompt the user if we have
+ // not already tried going DIRECT. This is something that the
+ // classic codebase supported; however, IE6 does not prompt.
+
+ if (!pi->mNext)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("PAC failover from %s %s:%d to %s %s:%d\n",
+ pi->mType, pi->mHost.get(), pi->mPort,
+ pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort));
+
+ NS_ADDREF(*aResult = pi->mNext);
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::InsertFilterLink(FilterLink *link, uint32_t position)
+{
+ if (!mFilters) {
+ mFilters = link;
+ return NS_OK;
+ }
+
+ // insert into mFilters in sorted order
+ FilterLink *last = nullptr;
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ if (position < iter->position) {
+ if (last) {
+ link->next = last->next;
+ last->next = link;
+ }
+ else {
+ link->next = mFilters;
+ mFilters = link;
+ }
+ return NS_OK;
+ }
+ last = iter;
+ }
+ // our position is equal to or greater than the last link in the list
+ last->next = link;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter *filter,
+ uint32_t position)
+{
+ UnregisterFilter(filter); // remove this filter if we already have it
+
+ FilterLink *link = new FilterLink(position, filter);
+ if (!link) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return InsertFilterLink(link, position);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter,
+ uint32_t position)
+{
+ UnregisterChannelFilter(channelFilter); // remove this filter if we already have it
+
+ FilterLink *link = new FilterLink(position, channelFilter);
+ if (!link) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return InsertFilterLink(link, position);
+}
+
+nsresult
+nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject)
+{
+ FilterLink *last = nullptr;
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ nsCOMPtr<nsISupports> object = do_QueryInterface(iter->filter);
+ nsCOMPtr<nsISupports> object2 = do_QueryInterface(iter->channelFilter);
+ if (object == givenObject || object2 == givenObject) {
+ if (last)
+ last->next = iter->next;
+ else
+ mFilters = iter->next;
+ iter->next = nullptr;
+ delete iter;
+ return NS_OK;
+ }
+ last = iter;
+ }
+
+ // No need to throw an exception in this case.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter *filter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(filter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(channelFilter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType)
+{
+ *aProxyConfigType = mProxyConfig;
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters)
+{
+ // check to see the owners flag? /!?/ TODO
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+
+ if (aFilters.IsEmpty()) {
+ return;
+ }
+
+ //
+ // filter = ( host | domain | ipaddr ["/" mask] ) [":" port]
+ // filters = filter *( "," LWS filter)
+ //
+ // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref string
+ mFilterLocalHosts = false;
+
+ mozilla::Tokenizer t(aFilters);
+ mozilla::Tokenizer::Token token;
+ bool eof = false;
+ // while (*filters) {
+ while (!eof) {
+ // skip over spaces and ,
+ t.SkipWhites();
+ while (t.CheckChar(',')) {
+ t.SkipWhites();
+ }
+
+ nsAutoCString portStr;
+ nsAutoCString hostStr;
+ nsAutoCString maskStr;
+ t.Record();
+
+ bool parsingIPv6 = false;
+ bool parsingPort = false;
+ bool parsingMask = false;
+ while (t.Next(token)) {
+ if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) {
+ eof = true;
+ break;
+ }
+ if (token.Equals(mozilla::Tokenizer::Token::Char(',')) ||
+ token.Type() == mozilla::Tokenizer::TOKEN_WS) {
+ break;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ parsingIPv6 = true;
+ continue;
+ }
+
+ if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) {
+ // Port is starting. Claim the previous as host.
+ if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ t.Claim(hostStr);
+ }
+ t.Record();
+ parsingPort = true;
+ continue;
+ } else if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
+ t.Claim(hostStr);
+ t.Record();
+ parsingMask = true;
+ continue;
+ } else if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) {
+ parsingIPv6 = false;
+ continue;
+ }
+ }
+ if (!parsingPort && !parsingMask) {
+ t.Claim(hostStr);
+ } else if (parsingPort) {
+ t.Claim(portStr);
+ } else if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ NS_WARNING("Could not parse this rule");
+ continue;
+ }
+
+ if (hostStr.IsEmpty()) {
+ continue;
+ }
+
+ // If the current host filter is "<local>", then all local (i.e.
+ // no dots in the hostname) hosts should bypass the proxy
+ if (hostStr.EqualsIgnoreCase("<local>")) {
+ mFilterLocalHosts = true;
+ LOG(("loaded filter for local hosts "
+ "(plain host names, no dots)\n"));
+ // Continue to next host filter;
+ continue;
+ }
+
+ // For all other host filters, create HostInfo object and add to list
+ HostInfo *hinfo = new HostInfo();
+ nsresult rv = NS_OK;
+
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ port = 0;
+ }
+ hinfo->port = port;
+
+ int32_t maskLen = maskStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ maskLen = 128;
+ }
+
+ // PR_StringToNetAddr can't parse brackets enclosed IPv6
+ nsAutoCString addrString = hostStr;
+ if (hostStr.First() == '[' && hostStr.Last() == ']') {
+ addrString = Substring(hostStr, 1, hostStr.Length() - 2);
+ }
+
+ PRNetAddr addr;
+ if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) {
+ hinfo->is_ipaddr = true;
+ hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6
+ hinfo->ip.mask_len = maskLen;
+
+ if (hinfo->ip.mask_len == 0) {
+ NS_WARNING("invalid mask");
+ goto loser;
+ }
+
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr);
+ // adjust mask_len accordingly
+ if (hinfo->ip.mask_len <= 32)
+ hinfo->ip.mask_len += 96;
+ }
+ else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ }
+ else {
+ NS_WARNING("unknown address family");
+ goto loser;
+ }
+
+ // apply mask to IPv6 address
+ proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len);
+ }
+ else {
+ nsAutoCString host;
+ if (hostStr.First() == '*') {
+ host = Substring(hostStr, 1);
+ } else {
+ host = hostStr;
+ }
+
+ if (host.IsEmpty()) {
+ hinfo->name.host = nullptr;
+ goto loser;
+ }
+
+ hinfo->name.host_len = host.Length();
+
+ hinfo->is_ipaddr = false;
+ hinfo->name.host = ToNewCString(host);
+
+ if (!hinfo->name.host)
+ goto loser;
+ }
+
+//#define DEBUG_DUMP_FILTERS
+#ifdef DEBUG_DUMP_FILTERS
+ printf("loaded filter[%zu]:\n", mHostFiltersArray.Length());
+ printf(" is_ipaddr = %u\n", hinfo->is_ipaddr);
+ printf(" port = %u\n", hinfo->port);
+ printf(" host = %s\n", hostStr.get());
+ if (hinfo->is_ipaddr) {
+ printf(" ip.family = %x\n", hinfo->ip.family);
+ printf(" ip.mask_len = %u\n", hinfo->ip.mask_len);
+
+ PRNetAddr netAddr;
+ PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr);
+ memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr));
+
+ char buf[256];
+ PR_NetAddrToString(&netAddr, buf, sizeof(buf));
+
+ printf(" ip.addr = %s\n", buf);
+ }
+ else {
+ printf(" name.host = %s\n", hinfo->name.host);
+ }
+#endif
+
+ mHostFiltersArray.AppendElement(hinfo);
+ hinfo = nullptr;
+loser:
+ delete hinfo;
+ }
+}
+
+nsresult
+nsProtocolProxyService::GetProtocolInfo(nsIURI *uri, nsProtocolInfo *info)
+{
+ NS_PRECONDITION(uri, "URI is null");
+ NS_PRECONDITION(info, "info is null");
+
+ nsresult rv;
+
+ rv = uri->GetScheme(info->scheme);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = handler->DoGetProtocolFlags(uri, &info->flags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = handler->GetDefaultPort(&info->defaultPort);
+ return rv;
+}
+
+nsresult
+nsProtocolProxyService::NewProxyInfo_Internal(const char *aType,
+ const nsACString &aHost,
+ int32_t aPort,
+ const nsACString &aUsername,
+ const nsACString &aPassword,
+ uint32_t aFlags,
+ uint32_t aFailoverTimeout,
+ nsIProxyInfo *aFailoverProxy,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **aResult)
+{
+ if (aPort <= 0)
+ aPort = -1;
+
+ nsCOMPtr<nsProxyInfo> failover;
+ if (aFailoverProxy) {
+ failover = do_QueryInterface(aFailoverProxy);
+ NS_ENSURE_ARG(failover);
+ }
+
+ nsProxyInfo *proxyInfo = new nsProxyInfo();
+ if (!proxyInfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ proxyInfo->mType = aType;
+ proxyInfo->mHost = aHost;
+ proxyInfo->mPort = aPort;
+ proxyInfo->mUsername = aUsername;
+ proxyInfo->mPassword = aPassword;
+ proxyInfo->mFlags = aFlags;
+ proxyInfo->mResolveFlags = aResolveFlags;
+ proxyInfo->mTimeout = aFailoverTimeout == UINT32_MAX
+ ? mFailedProxyTimeout : aFailoverTimeout;
+ failover.swap(proxyInfo->mNext);
+
+ NS_ADDREF(*aResult = proxyInfo);
+ return NS_OK;
+}
+
+nsresult
+nsProtocolProxyService::Resolve_Internal(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ uint32_t flags,
+ bool *usePACThread,
+ nsIProxyInfo **result)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+ nsresult rv = SetupPACThread();
+ if (NS_FAILED(rv))
+ return rv;
+
+ *usePACThread = false;
+ *result = nullptr;
+
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
+ return NS_OK; // Can't proxy this (filters may not override)
+
+ nsCOMPtr<nsIURI> uri;
+ rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // See bug #586908.
+ // Avoid endless loop if |uri| is the current PAC-URI. Returning OK
+ // here means that we will not use a proxy for this connection.
+ if (mPACMan && mPACMan->IsPACURI(uri))
+ return NS_OK;
+
+ bool mainThreadOnly;
+ if (mSystemProxySettings &&
+ mProxyConfig == PROXYCONFIG_SYSTEM &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) {
+ // If the system proxy setting implementation is not threadsafe (e.g
+ // linux gconf), we'll do it inline here. Such implementations promise
+ // not to block
+
+ nsAutoCString PACURI;
+ nsAutoCString pacString;
+
+ if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty()) {
+ // There is a PAC URI configured. If it is unchanged, then
+ // just execute the PAC thread. If it is changed then load
+ // the new value
+
+ if (mPACMan && mPACMan->IsPACURI(PACURI)) {
+ // unchanged
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ ConfigureFromPAC(PACURI, false);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+
+ uri->GetAsciiSpec(spec);
+ uri->GetAsciiHost(host);
+ uri->GetScheme(scheme);
+ uri->GetPort(&port);
+
+ // now try the system proxy settings for this particular url
+ if (NS_SUCCEEDED(mSystemProxySettings->
+ GetProxyForURI(spec, scheme, host, port,
+ pacString))) {
+ ProcessPACString(pacString, 0, result);
+ return NS_OK;
+ }
+ }
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if (mProxyConfig == PROXYCONFIG_DIRECT ||
+ (mProxyConfig == PROXYCONFIG_MANUAL &&
+ !CanUseProxy(uri, info.defaultPort)))
+ return NS_OK;
+
+ // Proxy auto config magic...
+ if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD) {
+ // Do not query PAC now.
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ // If we aren't in manual proxy configuration mode then we don't
+ // want to honor any manual specific prefs that might be still set
+ if (mProxyConfig != PROXYCONFIG_MANUAL)
+ return NS_OK;
+
+ // proxy info values for manual configuration mode
+ const char *type = nullptr;
+ const nsACString *host = nullptr;
+ int32_t port = -1;
+
+ uint32_t proxyFlags = 0;
+
+ if ((flags & RESOLVE_PREFER_SOCKS_PROXY) &&
+ !mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4)
+ type = kProxyType_SOCKS4;
+ else
+ type = kProxyType_SOCKS;
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS)
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) &&
+ !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ }
+ else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 &&
+ ((flags & RESOLVE_IGNORE_URI_SCHEME) ||
+ info.scheme.EqualsLiteral("http"))) {
+ host = &mHTTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPProxyPort;
+ }
+ else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("https")) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ }
+ else if (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("ftp")) {
+ host = &mFTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mFTPProxyPort;
+ }
+ else if (!mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4)
+ type = kProxyType_SOCKS4;
+ else
+ type = kProxyType_SOCKS;
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS)
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ if (type) {
+ rv = NewProxyInfo_Internal(type, *host, port,
+ EmptyCString(), EmptyCString(),
+ proxyFlags, UINT32_MAX, nullptr, flags,
+ result);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo *aProxy)
+{
+ // Disable Prefetch in the DNS service if a proxy is in use.
+ if (!aProxy)
+ return;
+
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ if (!pi ||
+ !pi->mType ||
+ pi->mType == kProxyType_DIRECT)
+ return;
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns)
+ return;
+ nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns);
+ if (!pdns)
+ return;
+
+ // We lose the prefetch optimization for the life of the dns service.
+ pdns->SetPrefetchEnabled(false);
+}
+
+void
+nsProtocolProxyService::ApplyFilters(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ nsIProxyInfo **list)
+{
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
+ return;
+
+ // We prune the proxy list prior to invoking each filter. This may be
+ // somewhat inefficient, but it seems like a good idea since we want each
+ // filter to "see" a valid proxy list.
+
+ nsCOMPtr<nsIProxyInfo> result;
+
+ for (FilterLink *iter = mFilters; iter; iter = iter->next) {
+ PruneProxyInfo(info, list);
+ nsresult rv = NS_OK;
+ if (iter->filter) {
+ nsCOMPtr<nsIURI> uri;
+ rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (uri) {
+ rv = iter->filter->ApplyFilter(this, uri, *list,
+ getter_AddRefs(result));
+ }
+ } else if (iter->channelFilter) {
+ rv = iter->channelFilter->ApplyFilter(this, channel, *list,
+ getter_AddRefs(result));
+ }
+ if (NS_FAILED(rv))
+ continue;
+ result.swap(*list);
+ }
+
+ PruneProxyInfo(info, list);
+}
+
+void
+nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info,
+ nsIProxyInfo **list)
+{
+ if (!*list)
+ return;
+ nsProxyInfo *head = nullptr;
+ CallQueryInterface(*list, &head);
+ if (!head) {
+ NS_NOTREACHED("nsIProxyInfo must QI to nsProxyInfo");
+ return;
+ }
+ NS_RELEASE(*list);
+
+ // Pruning of disabled proxies works like this:
+ // - If all proxies are disabled, return the full list
+ // - Otherwise, remove the disabled proxies.
+ //
+ // Pruning of disallowed proxies works like this:
+ // - If the protocol handler disallows the proxy, then we disallow it.
+
+ // Start by removing all disallowed proxies if required:
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) {
+ nsProxyInfo *last = nullptr, *iter = head;
+ while (iter) {
+ if ((iter->Type() == kProxyType_HTTP) ||
+ (iter->Type() == kProxyType_HTTPS)) {
+ // reject!
+ if (last)
+ last->mNext = iter->mNext;
+ else
+ head = iter->mNext;
+ nsProxyInfo *next = iter->mNext;
+ iter->mNext = nullptr;
+ iter->Release();
+ iter = next;
+ } else {
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+ if (!head)
+ return;
+ }
+
+ // Now, scan to see if all remaining proxies are disabled. If so, then
+ // we'll just bail and return them all. Otherwise, we'll go and prune the
+ // disabled ones.
+
+ bool allDisabled = true;
+
+ nsProxyInfo *iter;
+ for (iter = head; iter; iter = iter->mNext) {
+ if (!IsProxyDisabled(iter)) {
+ allDisabled = false;
+ break;
+ }
+ }
+
+ if (allDisabled)
+ LOG(("All proxies are disabled, so trying all again"));
+ else {
+ // remove any disabled proxies.
+ nsProxyInfo *last = nullptr;
+ for (iter = head; iter; ) {
+ if (IsProxyDisabled(iter)) {
+ // reject!
+ nsProxyInfo *reject = iter;
+
+ iter = iter->mNext;
+ if (last)
+ last->mNext = iter;
+ else
+ head = iter;
+
+ reject->mNext = nullptr;
+ NS_RELEASE(reject);
+ continue;
+ }
+
+ // since we are about to use this proxy, make sure it is not on
+ // the disabled proxy list. we'll add it back to that list if
+ // we have to (in GetFailoverForProxy).
+ //
+ // XXX(darin): It might be better to do this as a final pass.
+ //
+ EnableProxy(iter);
+
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+
+ // if only DIRECT was specified then return no proxy info, and we're done.
+ if (head && !head->mNext && head->mType == kProxyType_DIRECT)
+ NS_RELEASE(head);
+
+ *list = head; // Transfer ownership
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h
new file mode 100644
index 000000000..9fe24699e
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProtocolProxyService_h__
+#define nsProtocolProxyService_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIProtocolProxyFilter.h"
+#include "nsIProxyInfo.h"
+#include "nsIObserver.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "prio.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+class nsISystemProxySettings;
+
+namespace mozilla {
+namespace net {
+
+typedef nsDataHashtable<nsCStringHashKey, uint32_t> nsFailedProxyTable;
+
+class nsPACMan;
+class nsProxyInfo;
+struct nsProtocolInfo;
+
+// CID for the nsProtocolProxyService class
+// 091eedd8-8bae-4fe3-ad62-0c87351e640d
+#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \
+{ 0x091eedd8, 0x8bae, 0x4fe3, \
+ { 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d } }
+
+class nsProtocolProxyService final : public nsIProtocolProxyService2
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYSERVICE2
+ NS_DECL_NSIPROTOCOLPROXYSERVICE
+ NS_DECL_NSIOBSERVER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+ nsProtocolProxyService();
+
+ nsresult Init();
+ nsresult DeprecatedBlockingResolve(nsIChannel *aChannel,
+ uint32_t aFlags,
+ nsIProxyInfo **retval);
+
+protected:
+ friend class nsAsyncResolveRequest;
+ friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest
+
+ ~nsProtocolProxyService();
+
+ /**
+ * This method is called whenever a preference may have changed or
+ * to initialize all preferences.
+ *
+ * @param prefs
+ * This must be a pointer to the root pref branch.
+ * @param name
+ * This can be the name of a fully-qualified preference, or it can
+ * be null, in which case all preferences will be initialized.
+ */
+ void PrefsChanged(nsIPrefBranch *prefs, const char *name);
+
+ /**
+ * This method is called to create a nsProxyInfo instance from the given
+ * PAC-style proxy string. It parses up to the end of the string, or to
+ * the next ';' character.
+ *
+ * @param proxy
+ * The PAC-style proxy string to parse. This must not be null.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * Upon return this points to a newly allocated nsProxyInfo or null
+ * if the proxy string was invalid.
+ *
+ * @return A pointer beyond the parsed proxy string (never null).
+ */
+ const char * ExtractProxyInfo(const char *proxy,
+ uint32_t aResolveFlags,
+ nsProxyInfo **result);
+
+ /**
+ * Load the specified PAC file.
+ *
+ * @param pacURI
+ * The URI spec of the PAC file to load.
+ */
+ nsresult ConfigureFromPAC(const nsCString &pacURI, bool forceReload);
+
+ /**
+ * This method builds a list of nsProxyInfo objects from the given PAC-
+ * style string.
+ *
+ * @param pacString
+ * The PAC-style proxy string to parse. This may be empty.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * The resulting list of proxy info objects.
+ */
+ void ProcessPACString(const nsCString &pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result);
+
+ /**
+ * This method generates a string valued identifier for the given
+ * nsProxyInfo object.
+ *
+ * @param pi
+ * The nsProxyInfo object from which to generate the key.
+ * @param result
+ * Upon return, this parameter holds the generated key.
+ */
+ void GetProxyKey(nsProxyInfo *pi, nsCString &result);
+
+ /**
+ * @return Seconds since start of session.
+ */
+ uint32_t SecondsSinceSessionStart();
+
+ /**
+ * This method removes the specified proxy from the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to enable.
+ */
+ void EnableProxy(nsProxyInfo *pi);
+
+ /**
+ * This method adds the specified proxy to the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to disable.
+ */
+ void DisableProxy(nsProxyInfo *pi);
+
+ /**
+ * This method tests to see if the given proxy is disabled.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to test.
+ *
+ * @return True if the specified proxy is disabled.
+ */
+ bool IsProxyDisabled(nsProxyInfo *pi);
+
+ /**
+ * This method queries the protocol handler for the given scheme to check
+ * for the protocol flags and default port.
+ *
+ * @param uri
+ * The URI to query.
+ * @param info
+ * Holds information about the protocol upon return. Pass address
+ * of structure when you call this method. This parameter must not
+ * be null.
+ */
+ nsresult GetProtocolInfo(nsIURI *uri, nsProtocolInfo *result);
+
+ /**
+ * This method is an internal version nsIProtocolProxyService::newProxyInfo
+ * that expects a string literal for the type.
+ *
+ * @param type
+ * The proxy type.
+ * @param host
+ * The proxy host name (UTF-8 ok).
+ * @param port
+ * The proxy port number.
+ * @param username
+ * The username for the proxy (ASCII). May be "", but not null.
+ * @param password
+ * The password for the proxy (ASCII). May be "", but not null.
+ * @param flags
+ * The proxy flags (nsIProxyInfo::flags).
+ * @param timeout
+ * The failover timeout for this proxy.
+ * @param next
+ * The next proxy to try if this one fails.
+ * @param aResolveFlags
+ * The flags passed to resolve (from nsIProtocolProxyService).
+ * @param result
+ * The resulting nsIProxyInfo object.
+ */
+ nsresult NewProxyInfo_Internal(const char *type,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &username,
+ const nsACString &password,
+ uint32_t flags,
+ uint32_t timeout,
+ nsIProxyInfo *next,
+ uint32_t aResolveFlags,
+ nsIProxyInfo **result);
+
+ /**
+ * This method is an internal version of Resolve that does not query PAC.
+ * It performs all of the built-in processing, and reports back to the
+ * caller with either the proxy info result or a flag to instruct the
+ * caller to use PAC instead.
+ *
+ * @param channel
+ * The channel to test.
+ * @param info
+ * Information about the URI's protocol.
+ * @param flags
+ * The flags passed to either the resolve or the asyncResolve method.
+ * @param usePAC
+ * If this flag is set upon return, then PAC should be queried to
+ * resolve the proxy info.
+ * @param result
+ * The resulting proxy info or null.
+ */
+ nsresult Resolve_Internal(nsIChannel *channel,
+ const nsProtocolInfo &info,
+ uint32_t flags,
+ bool *usePAC,
+ nsIProxyInfo **result);
+
+ /**
+ * This method applies the registered filters to the given proxy info
+ * list, and returns a possibly modified list.
+ *
+ * @param channel
+ * The channel corresponding to this proxy info list.
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info,
+ nsIProxyInfo **proxyInfo);
+
+ /**
+ * This method is a simple wrapper around ApplyFilters that takes the
+ * proxy info list inout param as a nsCOMPtr.
+ */
+ inline void ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info,
+ nsCOMPtr<nsIProxyInfo> &proxyInfo)
+ {
+ nsIProxyInfo *pi = nullptr;
+ proxyInfo.swap(pi);
+ ApplyFilters(channel, info, &pi);
+ proxyInfo.swap(pi);
+ }
+
+ /**
+ * This method prunes out disabled and disallowed proxies from a given
+ * proxy info list.
+ *
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void PruneProxyInfo(const nsProtocolInfo &info,
+ nsIProxyInfo **proxyInfo);
+
+ /**
+ * This method populates mHostFiltersArray from the given string.
+ *
+ * @param hostFilters
+ * A "no-proxy-for" exclusion list.
+ */
+ void LoadHostFilters(const nsACString& hostFilters);
+
+ /**
+ * This method checks the given URI against mHostFiltersArray.
+ *
+ * @param uri
+ * The URI to test.
+ * @param defaultPort
+ * The default port for the given URI.
+ *
+ * @return True if the URI can use the specified proxy.
+ */
+ bool CanUseProxy(nsIURI *uri, int32_t defaultPort);
+
+ /**
+ * Disable Prefetch in the DNS service if a proxy is in use.
+ *
+ * @param aProxy
+ * The proxy information
+ */
+ void MaybeDisableDNSPrefetch(nsIProxyInfo *aProxy);
+
+private:
+ nsresult SetupPACThread();
+ nsresult ResetPACThread();
+ nsresult ReloadNetworkPAC();
+
+public:
+ // The Sun Forte compiler and others implement older versions of the
+ // C++ standard's rules on access and nested classes. These structs
+ // need to be public in order to deal with those compilers.
+
+ struct HostInfoIP {
+ uint16_t family;
+ uint16_t mask_len;
+ PRIPv6Addr addr; // possibly IPv4-mapped address
+ };
+
+ struct HostInfoName {
+ char *host;
+ uint32_t host_len;
+ };
+
+protected:
+
+ // simplified array of filters defined by this struct
+ struct HostInfo {
+ bool is_ipaddr;
+ int32_t port;
+ union {
+ HostInfoIP ip;
+ HostInfoName name;
+ };
+
+ HostInfo()
+ : is_ipaddr(false)
+ , port(0)
+ { /* other members intentionally uninitialized */ }
+ ~HostInfo() {
+ if (!is_ipaddr && name.host)
+ free(name.host);
+ }
+ };
+
+ // An instance of this struct is allocated for each registered
+ // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter.
+ struct FilterLink {
+ struct FilterLink *next;
+ uint32_t position;
+ nsCOMPtr<nsIProtocolProxyFilter> filter;
+ nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter;
+ FilterLink(uint32_t p, nsIProtocolProxyFilter *f)
+ : next(nullptr), position(p), filter(f), channelFilter(nullptr) {}
+ FilterLink(uint32_t p, nsIProtocolProxyChannelFilter *cf)
+ : next(nullptr), position(p), filter(nullptr), channelFilter(cf) {}
+ // Chain deletion to simplify cleaning up the filter links
+ ~FilterLink() { if (next) delete next; }
+ };
+
+private:
+ // Private methods to insert and remove FilterLinks from the FilterLink chain.
+ nsresult InsertFilterLink(FilterLink *link, uint32_t position);
+ nsresult RemoveFilterLink(nsISupports *givenObject);
+
+protected:
+ // Indicates if local hosts (plain hostnames, no dots) should use the proxy
+ bool mFilterLocalHosts;
+
+ // Holds an array of HostInfo objects
+ nsTArray<nsAutoPtr<HostInfo> > mHostFiltersArray;
+
+ // Points to the start of a sorted by position, singly linked list
+ // of FilterLink objects.
+ FilterLink *mFilters;
+
+ uint32_t mProxyConfig;
+
+ nsCString mHTTPProxyHost;
+ int32_t mHTTPProxyPort;
+
+ nsCString mFTPProxyHost;
+ int32_t mFTPProxyPort;
+
+ nsCString mHTTPSProxyHost;
+ int32_t mHTTPSProxyPort;
+
+ // mSOCKSProxyTarget could be a host, a domain socket path,
+ // or a named-pipe name.
+ nsCString mSOCKSProxyTarget;
+ int32_t mSOCKSProxyPort;
+ int32_t mSOCKSProxyVersion;
+ bool mSOCKSProxyRemoteDNS;
+ bool mProxyOverTLS;
+
+ RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ PRTime mSessionStart;
+ nsFailedProxyTable mFailedProxies;
+ int32_t mFailedProxyTimeout;
+
+private:
+ nsresult AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
+ nsIProtocolProxyCallback *callback,
+ nsICancelable **result,
+ bool isSyncOK);
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService, NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsProtocolProxyService_h__
diff --git a/netwerk/base/nsProxyInfo.cpp b/netwerk/base/nsProxyInfo.cpp
new file mode 100644
index 000000000..8291711ab
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsProxyInfo.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+// Yes, we support QI to nsProxyInfo
+NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo)
+
+NS_IMETHODIMP
+nsProxyInfo::GetHost(nsACString &result)
+{
+ result = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPort(int32_t *result)
+{
+ *result = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetType(nsACString &result)
+{
+ result = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFlags(uint32_t *result)
+{
+ *result = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetResolveFlags(uint32_t *result)
+{
+ *result = mResolveFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetUsername(nsACString &result)
+{
+ result = mUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPassword(nsACString &result)
+{
+ result = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverTimeout(uint32_t *result)
+{
+ *result = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverProxy(nsIProxyInfo **result)
+{
+ NS_IF_ADDREF(*result = mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetFailoverProxy(nsIProxyInfo *proxy)
+{
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(proxy);
+ NS_ENSURE_ARG(pi);
+
+ pi.swap(mNext);
+ return NS_OK;
+}
+
+// These pointers are declared in nsProtocolProxyService.cpp and
+// comparison of mType by string pointer is valid within necko
+ extern const char kProxyType_HTTP[];
+ extern const char kProxyType_HTTPS[];
+ extern const char kProxyType_SOCKS[];
+ extern const char kProxyType_SOCKS4[];
+ extern const char kProxyType_SOCKS5[];
+ extern const char kProxyType_DIRECT[];
+
+bool
+nsProxyInfo::IsDirect()
+{
+ if (!mType)
+ return true;
+ return mType == kProxyType_DIRECT;
+}
+
+bool
+nsProxyInfo::IsHTTP()
+{
+ return mType == kProxyType_HTTP;
+}
+
+bool
+nsProxyInfo::IsHTTPS()
+{
+ return mType == kProxyType_HTTPS;
+}
+
+bool
+nsProxyInfo::IsSOCKS()
+{
+ return mType == kProxyType_SOCKS ||
+ mType == kProxyType_SOCKS4 || mType == kProxyType_SOCKS5;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h
new file mode 100644
index 000000000..007387b77
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProxyInfo_h__
+#define nsProxyInfo_h__
+
+#include "nsIProxyInfo.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+// Use to support QI nsIProxyInfo to nsProxyInfo
+#define NS_PROXYINFO_IID \
+{ /* ed42f751-825e-4cc2-abeb-3670711a8b85 */ \
+ 0xed42f751, \
+ 0x825e, \
+ 0x4cc2, \
+ {0xab, 0xeb, 0x36, 0x70, 0x71, 0x1a, 0x8b, 0x85} \
+}
+
+namespace mozilla {
+namespace net {
+
+// This class is exposed to other classes inside Necko for fast access
+// to the nsIProxyInfo attributes.
+class nsProxyInfo final : public nsIProxyInfo
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYINFO
+
+ // Cheap accessors for use within Necko
+ const nsCString &Host() { return mHost; }
+ int32_t Port() { return mPort; }
+ const char *Type() { return mType; }
+ uint32_t Flags() { return mFlags; }
+ const nsCString &Username() { return mUsername; }
+ const nsCString &Password() { return mPassword; }
+
+ bool IsDirect();
+ bool IsHTTP();
+ bool IsHTTPS();
+ bool IsSOCKS();
+
+private:
+ friend class nsProtocolProxyService;
+
+ explicit nsProxyInfo(const char *type = nullptr)
+ : mType(type)
+ , mPort(-1)
+ , mFlags(0)
+ , mResolveFlags(0)
+ , mTimeout(UINT32_MAX)
+ , mNext(nullptr)
+ {}
+
+ ~nsProxyInfo()
+ {
+ NS_IF_RELEASE(mNext);
+ }
+
+ const char *mType; // pointer to statically allocated value
+ nsCString mHost;
+ nsCString mUsername;
+ nsCString mPassword;
+ int32_t mPort;
+ uint32_t mFlags;
+ uint32_t mResolveFlags;
+ uint32_t mTimeout;
+ nsProxyInfo *mNext;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsProxyInfo_h__
diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h
new file mode 100644
index 000000000..3482330e6
--- /dev/null
+++ b/netwerk/base/nsReadLine.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsReadLine_h__
+#define nsReadLine_h__
+
+#include "nsIInputStream.h"
+#include "mozilla/Likely.h"
+
+/**
+ * @file
+ * Functions to read complete lines from an input stream.
+ *
+ * To properly use the helper function in here (NS_ReadLine) the caller should
+ * create a nsLineBuffer<T> with new, and pass it to NS_ReadLine every time it
+ * wants a line out.
+ *
+ * When done, the object should be deleted.
+ */
+
+/**
+ * @internal
+ * Buffer size. This many bytes will be buffered. If a line is longer than this,
+ * the partial line will be appended to the out parameter of NS_ReadLine and the
+ * buffer will be emptied.
+ * Note: if you change this constant, please update the regression test in
+ * netwerk/test/unit/test_readline.js accordingly (bug 397850).
+ */
+#define kLineBufferSize 4096
+
+/**
+ * @internal
+ * Line buffer structure, buffers data from an input stream.
+ * The buffer is empty when |start| == |end|.
+ * Invariant: |start| <= |end|
+ */
+template<typename CharT>
+class nsLineBuffer {
+ public:
+ nsLineBuffer() : start(buf), end(buf) { }
+
+ CharT buf[kLineBufferSize+1];
+ CharT* start;
+ CharT* end;
+};
+
+/**
+ * Read a line from an input stream. Lines are separated by '\r' (0x0D) or '\n'
+ * (0x0A), or "\r\n" or "\n\r".
+ *
+ * @param aStream
+ * The stream to read from
+ * @param aBuffer
+ * The line buffer to use. A single line buffer must not be used with
+ * different input streams.
+ * @param aLine [out]
+ * The string where the line will be stored.
+ * @param more [out]
+ * Whether more data is available in the buffer. If true, NS_ReadLine may
+ * be called again to read further lines. Otherwise, further calls to
+ * NS_ReadLine will return an error.
+ *
+ * @retval NS_OK
+ * Read successful
+ * @retval error
+ * Input stream returned an error upon read. See
+ * nsIInputStream::read.
+ */
+template<typename CharT, class StreamType, class StringType>
+nsresult
+NS_ReadLine (StreamType* aStream, nsLineBuffer<CharT> * aBuffer,
+ StringType & aLine, bool *more)
+{
+ CharT eolchar = 0; // the first eol char or 1 after \r\n or \n\r is found
+
+ aLine.Truncate();
+
+ while (1) { // will be returning out of this loop on eol or eof
+ if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it.
+ uint32_t bytesRead;
+ nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(bytesRead == 0)) {
+ *more = false;
+ return rv;
+ }
+ aBuffer->start = aBuffer->buf;
+ aBuffer->end = aBuffer->buf + bytesRead;
+ *(aBuffer->end) = '\0';
+ }
+
+ /*
+ * Walk the buffer looking for an end-of-line.
+ * There are 3 cases to consider:
+ * 1. the eol char is the last char in the buffer
+ * 2. the eol char + one more char at the end of the buffer
+ * 3. the eol char + two or more chars at the end of the buffer
+ * we need at least one char after the first eol char to determine if
+ * it's a \r\n or \n\r sequence (and skip over it), and we need one
+ * more char after the end-of-line to set |more| correctly.
+ */
+ CharT* current = aBuffer->start;
+ if (MOZ_LIKELY(eolchar == 0)) {
+ for ( ; current < aBuffer->end; ++current) {
+ if (*current == '\n' || *current == '\r') {
+ eolchar = *current;
+ *current++ = '\0';
+ aLine.Append(aBuffer->start);
+ break;
+ }
+ }
+ }
+ if (MOZ_LIKELY(eolchar != 0)) {
+ for ( ; current < aBuffer->end; ++current) {
+ if ((eolchar == '\r' && *current == '\n') ||
+ (eolchar == '\n' && *current == '\r')) {
+ eolchar = 1;
+ continue;
+ }
+ aBuffer->start = current;
+ *more = true;
+ return NS_OK;
+ }
+ }
+
+ if (eolchar == 0)
+ aLine.Append(aBuffer->start);
+ aBuffer->start = aBuffer->end; // mark the buffer empty
+ }
+}
+
+#endif // nsReadLine_h__
diff --git a/netwerk/base/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp
new file mode 100644
index 000000000..4c3c718ba
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nscore.h"
+#include "nsRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRequestObserverProxyLog("nsRequestObserverProxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestObserverProxyLog, LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsARequestObserverEvent internal class...
+//-----------------------------------------------------------------------------
+
+nsARequestObserverEvent::nsARequestObserverEvent(nsIRequest *request)
+ : mRequest(request)
+{
+ NS_PRECONDITION(mRequest, "null pointer");
+}
+
+//-----------------------------------------------------------------------------
+// nsOnStartRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStartRequestEvent : public nsARequestObserverEvent
+{
+ RefPtr<nsRequestObserverProxy> mProxy;
+public:
+ nsOnStartRequestEvent(nsRequestObserverProxy *proxy,
+ nsIRequest *request)
+ : nsARequestObserverEvent(request)
+ , mProxy(proxy)
+ {
+ NS_PRECONDITION(mProxy, "null pointer");
+ }
+
+ virtual ~nsOnStartRequestEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsOnStartRequestEvent::HandleEvent [req=%x]\n", mRequest.get()));
+
+ if (!mProxy->mObserver) {
+ NS_NOTREACHED("already handled onStopRequest event (observer is null)");
+ return NS_OK;
+ }
+
+ LOG(("handle startevent=%p\n", this));
+ nsresult rv = mProxy->mObserver->OnStartRequest(mRequest, mProxy->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(("OnStartRequest failed [rv=%x] canceling request!\n", rv));
+ rv = mRequest->Cancel(rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!");
+ }
+
+ return NS_OK;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// nsOnStopRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStopRequestEvent : public nsARequestObserverEvent
+{
+ RefPtr<nsRequestObserverProxy> mProxy;
+public:
+ nsOnStopRequestEvent(nsRequestObserverProxy *proxy,
+ nsIRequest *request)
+ : nsARequestObserverEvent(request)
+ , mProxy(proxy)
+ {
+ NS_PRECONDITION(mProxy, "null pointer");
+ }
+
+ virtual ~nsOnStopRequestEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ LOG(("nsOnStopRequestEvent::HandleEvent [req=%x]\n", mRequest.get()));
+
+ nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver;
+ if (!observer) {
+ NS_NOTREACHED("already handled onStopRequest event (observer is null)");
+ return NS_OK;
+ }
+ // Do not allow any more events to be handled after OnStopRequest
+ mProxy->mObserver = 0;
+
+ nsresult status = NS_OK;
+ DebugOnly<nsresult> rv = mRequest->GetStatus(&status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetStatus failed for request!");
+
+ LOG(("handle stopevent=%p\n", this));
+ (void) observer->OnStopRequest(mRequest, mProxy->mContext, status);
+
+ return NS_OK;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsISupports implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRequestObserverProxy,
+ nsIRequestObserver,
+ nsIRequestObserverProxy)
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserver implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ MOZ_ASSERT(!context || context == mContext);
+ LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%x]\n", this, request));
+
+ nsOnStartRequestEvent *ev =
+ new nsOnStartRequestEvent(this, request);
+ if (!ev)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ LOG(("post startevent=%p\n", ev));
+ nsresult rv = FireEvent(ev);
+ if (NS_FAILED(rv))
+ delete ev;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ MOZ_ASSERT(!context || context == mContext);
+ LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%x status=%x]\n",
+ this, request, status));
+
+ // The status argument is ignored because, by the time the OnStopRequestEvent
+ // is actually processed, the status of the request may have changed :-(
+ // To make sure that an accurate status code is always used, GetStatus() is
+ // called when the OnStopRequestEvent is actually processed (see above).
+
+ nsOnStopRequestEvent *ev =
+ new nsOnStopRequestEvent(this, request);
+ if (!ev)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ LOG(("post stopevent=%p\n", ev));
+ nsresult rv = FireEvent(ev);
+ if (NS_FAILED(rv))
+ delete ev;
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::Init(nsIRequestObserver *observer, nsISupports *context)
+{
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>(observer);
+ mContext = new nsMainThreadPtrHolder<nsISupports>(context);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+nsresult
+nsRequestObserverProxy::FireEvent(nsARequestObserverEvent *event)
+{
+ nsCOMPtr<nsIEventTarget> mainThread(do_GetMainThread());
+ return mainThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRequestObserverProxy.h b/netwerk/base/nsRequestObserverProxy.h
new file mode 100644
index 000000000..0ad07186b
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRequestObserverProxy_h__
+#define nsRequestObserverProxy_h__
+
+#include "nsIRequestObserver.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+class nsARequestObserverEvent;
+
+class nsRequestObserverProxy final : public nsIRequestObserverProxy
+{
+ ~nsRequestObserverProxy() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIREQUESTOBSERVERPROXY
+
+ nsRequestObserverProxy() {}
+
+ nsIRequestObserver *Observer() { return mObserver; }
+
+ nsresult FireEvent(nsARequestObserverEvent *);
+
+protected:
+ nsMainThreadPtrHandle<nsIRequestObserver> mObserver;
+ nsMainThreadPtrHandle<nsISupports> mContext;
+
+ friend class nsOnStartRequestEvent;
+ friend class nsOnStopRequestEvent;
+};
+
+class nsARequestObserverEvent : public Runnable
+{
+public:
+ explicit nsARequestObserverEvent(nsIRequest *);
+
+protected:
+ virtual ~nsARequestObserverEvent() {}
+
+ nsCOMPtr<nsIRequest> mRequest;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRequestObserverProxy_h__
diff --git a/netwerk/base/nsSecCheckWrapChannel.cpp b/netwerk/base/nsSecCheckWrapChannel.cpp
new file mode 100644
index 000000000..330079a0f
--- /dev/null
+++ b/netwerk/base/nsSecCheckWrapChannel.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentSecurityManager.h"
+#include "nsSecCheckWrapChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIStreamListener.h"
+#include "mozilla/Logging.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gChannelWrapperLog("ChannelWrapper");
+#define CHANNELWRAPPERLOG(args) MOZ_LOG(gChannelWrapperLog, LogLevel::Debug, args)
+
+NS_IMPL_ADDREF(nsSecCheckWrapChannelBase)
+NS_IMPL_RELEASE(nsSecCheckWrapChannelBase)
+
+NS_INTERFACE_MAP_BEGIN(nsSecCheckWrapChannelBase)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal, mHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel2, mUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISecCheckWrapChannel)
+NS_INTERFACE_MAP_END
+
+//---------------------------------------------------------
+// nsSecCheckWrapChannelBase implementation
+//---------------------------------------------------------
+
+nsSecCheckWrapChannelBase::nsSecCheckWrapChannelBase(nsIChannel* aChannel)
+ : mChannel(aChannel)
+ , mHttpChannel(do_QueryInterface(aChannel))
+ , mHttpChannelInternal(do_QueryInterface(aChannel))
+ , mRequest(do_QueryInterface(aChannel))
+ , mUploadChannel(do_QueryInterface(aChannel))
+ , mUploadChannel2(do_QueryInterface(aChannel))
+{
+ MOZ_ASSERT(mChannel, "can not create a channel wrapper without a channel");
+}
+
+nsSecCheckWrapChannelBase::~nsSecCheckWrapChannelBase()
+{
+}
+
+//---------------------------------------------------------
+// nsISecCheckWrapChannel implementation
+//---------------------------------------------------------
+
+NS_IMETHODIMP
+nsSecCheckWrapChannelBase::GetInnerChannel(nsIChannel **aInnerChannel)
+{
+ NS_IF_ADDREF(*aInnerChannel = mChannel);
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+// nsSecCheckWrapChannel implementation
+//---------------------------------------------------------
+
+nsSecCheckWrapChannel::nsSecCheckWrapChannel(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo)
+ : nsSecCheckWrapChannelBase(aChannel)
+ , mLoadInfo(aLoadInfo)
+{
+ {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::nsSecCheckWrapChannel [%p] (%s)",
+ this, uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+}
+
+// static
+already_AddRefed<nsIChannel>
+nsSecCheckWrapChannel::MaybeWrap(nsIChannel* aChannel, nsILoadInfo* aLoadInfo)
+{
+ // Maybe a custom protocol handler actually returns a gecko
+ // http/ftpChannel - To check this we will check whether the channel
+ // implements a gecko non-scriptable interface e.g. nsIForcePendingChannel.
+ nsCOMPtr<nsIForcePendingChannel> isGeckoChannel = do_QueryInterface(aChannel);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (isGeckoChannel) {
+ // If it is a gecko channel (ftp or http) we do not need to wrap it.
+ channel = aChannel;
+ channel->SetLoadInfo(aLoadInfo);
+ } else {
+ channel = new nsSecCheckWrapChannel(aChannel, aLoadInfo);
+ }
+ return channel.forget();
+}
+
+nsSecCheckWrapChannel::~nsSecCheckWrapChannel()
+{
+}
+
+//---------------------------------------------------------
+// SecWrapChannelStreamListener helper
+//---------------------------------------------------------
+
+class SecWrapChannelStreamListener final : public nsIStreamListener
+{
+ public:
+ SecWrapChannelStreamListener(nsIRequest *aRequest,
+ nsIStreamListener *aStreamListener)
+ : mRequest(aRequest)
+ , mListener(aStreamListener) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ private:
+ ~SecWrapChannelStreamListener() {}
+
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+NS_IMPL_ISUPPORTS(SecWrapChannelStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return mListener->OnStartRequest(mRequest, aContext);
+}
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return mListener->OnStopRequest(mRequest, aContext, aStatus);
+}
+
+NS_IMETHODIMP
+SecWrapChannelStreamListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ return mListener->OnDataAvailable(mRequest, aContext, aInStream, aOffset, aCount);
+}
+
+//---------------------------------------------------------
+// nsIChannel implementation
+//---------------------------------------------------------
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::GetLoadInfo() [%p]",this));
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::SetLoadInfo() [%p]", this));
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> secWrapChannelListener =
+ new SecWrapChannelStreamListener(this, aListener);
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, secWrapChannelListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(secWrapChannelListener, nullptr);
+}
+
+NS_IMETHODIMP
+nsSecCheckWrapChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSecCheckWrapChannel.h b/netwerk/base/nsSecCheckWrapChannel.h
new file mode 100644
index 000000000..35300e0ea
--- /dev/null
+++ b/netwerk/base/nsSecCheckWrapChannel.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSecCheckWrapChannel_h__
+#define nsSecCheckWrapChannel_h__
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIUploadChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsISecCheckWrapChannel.h"
+#include "nsIWyciwygChannel.h"
+#include "mozilla/LoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+/*
+ * The nsSecCheckWrapChannelBase wraps channels that do *not*
+ * * provide a newChannel2() implementation
+ * * provide get/setLoadInfo functions
+ *
+ * In order to perform security checks for channels
+ * a) before opening the channel, and
+ * b) after redirects
+ * we are attaching a loadinfo object to every channel which
+ * provides information about the content-type of the channel,
+ * who initiated the load, etc.
+ *
+ * Addon created channels might *not* provide that loadInfo object for
+ * some transition time before we mark the NewChannel-API as deprecated.
+ * We do not want to break those addons hence we wrap such channels
+ * using the provided wrapper in this class.
+ *
+ * Please note that the wrapper only forwards calls for
+ * * nsIRequest
+ * * nsIChannel
+ * * nsIHttpChannel
+ * * nsIHttpChannelInternal
+ * * nsIUploadChannel
+ * * nsIUploadChannel2
+ *
+ * In case any addon needs to query the inner channel this class
+ * provides a readonly function to query the wrapped channel.
+ *
+ */
+
+class nsSecCheckWrapChannelBase : public nsIHttpChannel
+ , public nsIHttpChannelInternal
+ , public nsISecCheckWrapChannel
+ , public nsIUploadChannel
+ , public nsIUploadChannel2
+{
+public:
+ NS_FORWARD_NSIHTTPCHANNEL(mHttpChannel->)
+ NS_FORWARD_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal->)
+ NS_FORWARD_NSICHANNEL(mChannel->)
+ NS_FORWARD_NSIREQUEST(mRequest->)
+ NS_FORWARD_NSIUPLOADCHANNEL(mUploadChannel->)
+ NS_FORWARD_NSIUPLOADCHANNEL2(mUploadChannel2->)
+ NS_DECL_NSISECCHECKWRAPCHANNEL
+ NS_DECL_ISUPPORTS
+
+ explicit nsSecCheckWrapChannelBase(nsIChannel* aChannel);
+
+protected:
+ virtual ~nsSecCheckWrapChannelBase();
+
+ nsCOMPtr<nsIChannel> mChannel;
+ // We do a QI in the constructor to set the following pointers.
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal;
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIUploadChannel2> mUploadChannel2;
+};
+
+/* We define a separate class here to make it clear that we're overriding
+ * Get/SetLoadInfo as well as AsyncOpen2() and Open2(), rather that using
+ * the forwarded implementations provided by NS_FORWARD_NSICHANNEL"
+ */
+class nsSecCheckWrapChannel : public nsSecCheckWrapChannelBase
+{
+public:
+ NS_IMETHOD GetLoadInfo(nsILoadInfo **aLoadInfo);
+ NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo);
+
+ NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener);
+ NS_IMETHOD Open2(nsIInputStream** aStream);
+
+ nsSecCheckWrapChannel(nsIChannel* aChannel, nsILoadInfo* aLoadInfo);
+ static already_AddRefed<nsIChannel> MaybeWrap(nsIChannel* aChannel,
+ nsILoadInfo* aLoadInfo);
+
+protected:
+ virtual ~nsSecCheckWrapChannel();
+
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSecCheckWrapChannel_h__
diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp
new file mode 100644
index 000000000..68f971eae
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.cpp
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSerializationHelper.h"
+
+
+#include "mozilla/Base64.h"
+#include "nsISerializable.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsString.h"
+#include "nsBase64Encoder.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+
+nsresult
+NS_SerializeToString(nsISerializable* obj, nsCSubstring& str)
+{
+ RefPtr<nsBase64Encoder> stream(new nsBase64Encoder());
+ if (!stream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObjectOutputStream> objstream =
+ do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+ if (!objstream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ objstream->SetOutputStream(stream);
+ nsresult rv =
+ objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return stream->Finish(str);
+}
+
+nsresult
+NS_DeserializeObject(const nsCSubstring& str, nsISupports** obj)
+{
+ nsCString decodedData;
+ nsresult rv = Base64Decode(str, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectInputStream> objstream =
+ do_CreateInstance("@mozilla.org/binaryinputstream;1");
+ if (!objstream)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ objstream->SetInputStream(stream);
+ return objstream->ReadObject(true, obj);
+}
+
+NS_IMPL_ISUPPORTS(nsSerializationHelper, nsISerializationHelper)
+
+NS_IMETHODIMP
+nsSerializationHelper::SerializeToString(nsISerializable *serializable,
+ nsACString & _retval)
+{
+ return NS_SerializeToString(serializable, _retval);
+}
+
+NS_IMETHODIMP
+nsSerializationHelper::DeserializeObject(const nsACString & input,
+ nsISupports **_retval)
+{
+ return NS_DeserializeObject(input, _retval);
+}
diff --git a/netwerk/base/nsSerializationHelper.h b/netwerk/base/nsSerializationHelper.h
new file mode 100644
index 000000000..4a0b7339f
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** @file
+ * Helper functions for (de)serializing objects to/from ASCII strings.
+ */
+
+#ifndef NSSERIALIZATIONHELPER_H_
+#define NSSERIALIZATIONHELPER_H_
+
+#include "nsStringFwd.h"
+#include "nsISerializationHelper.h"
+#include "mozilla/Attributes.h"
+
+class nsISerializable;
+
+/**
+ * Serialize an object to an ASCII string.
+ */
+nsresult NS_SerializeToString(nsISerializable* obj,
+ nsCSubstring& str);
+
+/**
+ * Deserialize an object.
+ */
+nsresult NS_DeserializeObject(const nsCSubstring& str,
+ nsISupports** obj);
+
+class nsSerializationHelper final : public nsISerializationHelper
+{
+ ~nsSerializationHelper() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERIALIZATIONHELPER
+};
+
+#endif
diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp
new file mode 100644
index 000000000..8f5d3b029
--- /dev/null
+++ b/netwerk/base/nsServerSocket.cpp
@@ -0,0 +1,560 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+#include "nsServerSocket.h"
+#include "nsProxyRelease.h"
+#include "nsAutoPtr.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/net/DNS.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+
+namespace mozilla { namespace net {
+
+static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsServerSocket:: *nsServerSocketFunc)(void);
+
+static nsresult
+PostEvent(nsServerSocket *s, nsServerSocketFunc func)
+{
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(s, func);
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket
+//-----------------------------------------------------------------------------
+
+nsServerSocket::nsServerSocket()
+ : mFD(nullptr)
+ , mLock("nsServerSocket.mLock")
+ , mAttached(false)
+ , mKeepWhenOffline(false)
+{
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService)
+ {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID);
+ }
+ // make sure the STS sticks around as long as we do
+ NS_IF_ADDREF(gSocketTransportService);
+}
+
+nsServerSocket::~nsServerSocket()
+{
+ Close(); // just in case :)
+
+ // release our reference to the STS
+ nsSocketTransportService *serv = gSocketTransportService;
+ NS_IF_RELEASE(serv);
+}
+
+void
+nsServerSocket::OnMsgClose()
+{
+ SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then we'll close the socket in our OnSocketDetached.
+ // otherwise, call OnSocketDetached from here.
+ if (!mAttached)
+ OnSocketDetached(mFD);
+}
+
+void
+nsServerSocket::OnMsgAttach()
+{
+ SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition))
+ {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult
+nsServerSocket::TryAttach()
+{
+ nsresult rv;
+
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket())
+ {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsServerSocket::OnMsgAttach);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+void
+nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr)
+{
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ mListener->OnSocketAccepted(this, trans);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void
+nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL))
+ {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRFileDesc *clientFD;
+ PRNetAddr prClientAddr;
+ NetAddr clientAddr;
+
+ // NSPR doesn't tell us the peer address's length (as provided by the
+ // 'accept' system call), so we can't distinguish between named,
+ // unnamed, and abstract peer addresses. Clear prClientAddr first, so
+ // that the path will at least be reliably empty for unnamed and
+ // abstract addresses, and not garbage when the peer is unnamed.
+ memset(&prClientAddr, 0, sizeof(prClientAddr));
+
+ clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
+ PRNetAddrToNetAddr(&prClientAddr, &clientAddr);
+ if (!clientFD) {
+ NS_WARNING("PR_Accept failed");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ // Accept succeeded, create socket transport and notify consumer
+ CreateClientTransport(clientFD, clientAddr);
+}
+
+void
+nsServerSocket::OnSocketDetached(PRFileDesc *fd)
+{
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = NS_ERROR_ABORT;
+
+ if (mFD)
+ {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+
+ if (mListener)
+ {
+ mListener->OnStopListening(this, mCondition);
+
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIServerSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = mListener.forget();
+ }
+
+ // XXX we need to proxy the release to the listener's target thread to work
+ // around bug 337492.
+ if (listener) {
+ NS_ProxyRelease(mListenerTarget, listener.forget());
+ }
+ }
+}
+
+void
+nsServerSocket::IsLocal(bool *aIsLocal)
+{
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mAddr.raw.family == PR_AF_LOCAL)
+ {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ // If bound to loopback, this server socket only accepts local connections.
+ *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
+}
+
+void
+nsServerSocket::KeepWhenOffline(bool *aKeepWhenOffline)
+{
+ *aKeepWhenOffline = mKeepWhenOffline;
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket)
+
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsIServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog)
+{
+ return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog)
+{
+#if defined(XP_UNIX)
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Create a Unix domain PRNetAddr referring to the given path.
+ PRNetAddr addr;
+ if (path.Length() > sizeof(addr.local.path) - 1)
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ addr.local.family = PR_AF_LOCAL;
+ memcpy(addr.local.path, path.get(), path.Length());
+ addr.local.path[path.Length()] = '\0';
+
+ rv = InitWithAddress(&addr, aBacklog);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return aPath->SetPermissions(aPermissions);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
+ int32_t aBackLog)
+{
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+ if (aFlags & nsIServerSocket::LoopbackOnly)
+ val = PR_IpAddrLoopback;
+ else
+ val = PR_IpAddrAny;
+ PR_SetNetAddr(val, PR_AF_INET, aPort, &addr);
+
+ mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ nsresult rv;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenTCPSocket(aAddr->raw.family);
+ if (!mFD)
+ {
+ NS_WARNING("unable to create server socket");
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ if (PR_Bind(mFD, aAddr) != PR_SUCCESS)
+ {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ if (aBackLog < 0)
+ aBackLog = 5; // seems like a reasonable default
+
+ if (PR_Listen(mFD, aBackLog) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot listen on socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ // Set any additional socket defaults needed by child classes
+ rv = SetSocketDefaults();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ rv = ErrorAccordingToNSPR(PR_GetError());
+ Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsServerSocket::Close()
+{
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener)
+ {
+ if (mFD)
+ {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsServerSocket::OnMsgClose);
+}
+
+namespace {
+
+class ServerSocketListenerProxy final : public nsIServerSocketListener
+{
+ ~ServerSocketListenerProxy() {}
+
+public:
+ explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(aListener))
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ class OnSocketAcceptedRunnable : public Runnable
+ {
+ public:
+ OnSocketAcceptedRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ,
+ nsISocketTransport* aTransport)
+ : mListener(aListener)
+ , mServ(aServ)
+ , mTransport(aTransport)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mServ(aServ)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsresult mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(ServerSocketListenerProxy,
+ nsIServerSocketListener)
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport)
+{
+ RefPtr<OnSocketAcceptedRunnable> r =
+ new OnSocketAcceptedRunnable(mListener, aServ, aTransport);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aServ, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run()
+{
+ mListener->OnSocketAccepted(mServ, mTransport);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mServ, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsServerSocket::AsyncListen(nsIServerSocketListener *aListener)
+{
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListener = new ServerSocketListenerProxy(aListener);
+ mListenerTarget = NS_GetCurrentThread();
+ }
+
+ // Child classes may need to do additional setup just before listening begins
+ nsresult rv = OnSocketListen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return PostEvent(this, &nsServerSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetPort(int32_t *aResult)
+{
+ // no need to enter the lock here
+ uint16_t port;
+ if (mAddr.raw.family == PR_AF_INET)
+ port = mAddr.inet.port;
+ else if (mAddr.raw.family == PR_AF_INET6)
+ port = mAddr.ipv6.port;
+ else
+ return NS_ERROR_FAILURE;
+
+ *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetAddress(PRNetAddr *aResult)
+{
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsServerSocket.h b/netwerk/base/nsServerSocket.h
new file mode 100644
index 000000000..ef5f24231
--- /dev/null
+++ b/netwerk/base/nsServerSocket.h
@@ -0,0 +1,67 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerSocket_h__
+#define nsServerSocket_h__
+
+#include "prio.h"
+#include "nsASocketHandler.h"
+#include "nsIServerSocket.h"
+#include "mozilla/Mutex.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIEventTarget;
+namespace mozilla { namespace net {
+union NetAddr;
+
+class nsServerSocket : public nsASocketHandler
+ , public nsIServerSocket
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc *fd) override;
+ virtual void IsLocal(bool *aIsLocal) override;
+ virtual void KeepWhenOffline(bool *aKeepWhenOffline) override;
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+ nsServerSocket();
+
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const mozilla::net::NetAddr& clientAddr);
+ virtual nsresult SetSocketDefaults() { return NS_OK; }
+ virtual nsresult OnSocketListen() { return NS_OK; }
+
+protected:
+ virtual ~nsServerSocket();
+ PRFileDesc* mFD;
+ nsCOMPtr<nsIServerSocketListener> mListener;
+
+private:
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ // lock protects access to mListener; so it is not cleared while being used.
+ mozilla::Mutex mLock;
+ PRNetAddr mAddr;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ bool mKeepWhenOffline;
+};
+
+} // namespace net
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+
+#endif // nsServerSocket_h__
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
new file mode 100644
index 000000000..fd3f87a9c
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsSimpleNestedURI.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(nsSimpleNestedURI, nsSimpleURI, nsINestedURI)
+
+nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI)
+ : mInnerURI(innerURI)
+{
+ NS_ASSERTION(innerURI, "Must have inner URI");
+ NS_TryToSetImmutable(innerURI);
+}
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv = nsSimpleURI::Read(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(!mMutable, "How did that happen?");
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mInnerURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(mInnerURI);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mInnerURI);
+ if (!serializable) {
+ // We can't serialize ourselves
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = nsSimpleURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteCompoundObject(mInnerURI, NS_GET_IID(nsIURI),
+ true);
+ return rv;
+}
+
+// nsIIPCSerializableURI
+void
+nsSimpleNestedURI::Serialize(mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ SimpleNestedURIParams params;
+ URIParams simpleParams;
+
+ nsSimpleURI::Serialize(simpleParams);
+ params.simpleParams() = simpleParams;
+
+ SerializeURI(mInnerURI, params.innerURI());
+
+ aParams = params;
+}
+
+bool
+nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSimpleNestedURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleNestedURIParams& params = aParams.get_SimpleNestedURIParams();
+ if (!nsSimpleURI::Deserialize(params.simpleParams()))
+ return false;
+
+ mInnerURI = DeserializeURI(params.innerURI());
+
+ NS_TryToSetImmutable(mInnerURI);
+ return true;
+}
+
+// nsINestedURI
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnerURI(nsIURI** uri)
+{
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ return NS_EnsureSafeToReturn(mInnerURI, uri);
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnermostURI(nsIURI** uri)
+{
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+// nsSimpleURI overrides
+/* virtual */ nsresult
+nsSimpleNestedURI::EqualsInternal(nsIURI* other,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ bool* result)
+{
+ *result = false;
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ if (other) {
+ bool correctScheme;
+ nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (correctScheme) {
+ nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other);
+ if (nest) {
+ nsCOMPtr<nsIURI> otherInner;
+ rv = nest->GetInnerURI(getter_AddRefs(otherInner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (refHandlingMode == eHonorRef) ?
+ otherInner->Equals(mInnerURI, result) :
+ otherInner->EqualsExceptRef(mInnerURI, result);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI*
+nsSimpleNestedURI::StartClone(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv;
+ if (refHandlingMode == eHonorRef) {
+ rv = mInnerURI->Clone(getter_AddRefs(innerClone));
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = mInnerURI->CloneWithNewRef(newRef, getter_AddRefs(innerClone));
+ } else {
+ rv = mInnerURI->CloneIgnoringRef(getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone);
+ SetRefOnClone(url, refHandlingMode, newRef);
+ url->SetMutable(false);
+
+ return url;
+}
+
+// nsIClassInfo overrides
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ static NS_DEFINE_CID(kSimpleNestedURICID, NS_SIMPLENESTEDURI_CID);
+
+ *aClassIDNoAlloc = kSimpleNestedURICID;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h
new file mode 100644
index 000000000..2402ba4d3
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * URI class to be used for cases when a simple URI actually resolves to some
+ * other sort of URI, with the latter being what's loaded when the load
+ * happens. All objects of this class should always be immutable, so that the
+ * inner URI and this URI don't get out of sync. The Clone() implementation
+ * will guarantee this for the clone, but it's up to the protocol handlers
+ * creating these URIs to ensure that in the first place. The innerURI passed
+ * to this URI will be set immutable if possible.
+ */
+
+#ifndef nsSimpleNestedURI_h__
+#define nsSimpleNestedURI_h__
+
+#include "nsCOMPtr.h"
+#include "nsSimpleURI.h"
+#include "nsINestedURI.h"
+
+#include "nsIIPCSerializableURI.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleNestedURI : public nsSimpleURI,
+ public nsINestedURI
+{
+protected:
+ ~nsSimpleNestedURI() {}
+
+public:
+ // To be used by deserialization only. Leaves this object in an
+ // uninitialized state that will throw on most accesses.
+ nsSimpleNestedURI()
+ {
+ }
+
+ // Constructor that should generally be used when constructing an object of
+ // this class with |operator new|.
+ explicit nsSimpleNestedURI(nsIURI* innerURI);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINESTEDURI
+
+ // Overrides for various methods nsSimpleURI implements follow.
+
+ // nsSimpleURI overrides
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result) override;
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override;
+
+ // nsISerializable overrides
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ // nsIIPCSerializableURI overrides
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ // Override the nsIClassInfo method GetClassIDNoAlloc to make sure our
+ // nsISerializable impl works right.
+ NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) override;
+
+protected:
+ nsCOMPtr<nsIURI> mInnerURI;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsSimpleNestedURI_h__ */
diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp
new file mode 100644
index 000000000..da00de281
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSimpleStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+//
+//----------------------------------------------------------------------------
+// nsISupports implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMPL_ISUPPORTS(nsSimpleStreamListener,
+ nsISimpleStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+//
+//----------------------------------------------------------------------------
+// nsIRequestObserver implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return mObserver ?
+ mObserver->OnStartRequest(aRequest, aContext) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStopRequest(nsIRequest* request,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return mObserver ?
+ mObserver->OnStopRequest(request, aContext, aStatus) : NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsIStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnDataAvailable(nsIRequest* request,
+ nsISupports *aContext,
+ nsIInputStream *aSource,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t writeCount;
+ nsresult rv = mSink->WriteFrom(aSource, aCount, &writeCount);
+ //
+ // Equate zero bytes read and NS_SUCCEEDED to stopping the read.
+ //
+ if (NS_SUCCEEDED(rv) && (writeCount == 0))
+ return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsISimpleStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::Init(nsIOutputStream *aSink,
+ nsIRequestObserver *aObserver)
+{
+ NS_PRECONDITION(aSink, "null output stream");
+
+ mSink = aSink;
+ mObserver = aObserver;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleStreamListener.h b/netwerk/base/nsSimpleStreamListener.h
new file mode 100644
index 000000000..05f78a5bb
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleStreamListener_h__
+#define nsSimpleStreamListener_h__
+
+#include "nsISimpleStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleStreamListener : public nsISimpleStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISIMPLESTREAMLISTENER
+
+ nsSimpleStreamListener() { }
+
+protected:
+ virtual ~nsSimpleStreamListener() {}
+
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp
new file mode 100644
index 000000000..ae5c51a1e
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -0,0 +1,847 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#undef LOG
+#include "IPCMessageUtils.h"
+
+#include "nsSimpleURI.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsEscape.h"
+#include "nsError.h"
+#include "nsIIPCSerializableURI.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSimpleURI methods:
+
+nsSimpleURI::nsSimpleURI()
+ : mMutable(true)
+ , mIsRefValid(false)
+ , mIsQueryValid(false)
+{
+}
+
+nsSimpleURI::~nsSimpleURI()
+{
+}
+
+NS_IMPL_ADDREF(nsSimpleURI)
+NS_IMPL_RELEASE(nsSimpleURI)
+NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
+NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsIURIWithQuery, nsISerializable,
+ nsIClassInfo, nsIMutable, nsIIPCSerializableURI)
+NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ if (aIID.Equals(kThisSimpleURIImplementationCID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsSimpleURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv;
+
+ bool isMutable;
+ rv = aStream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ mMutable = isMutable;
+
+ rv = aStream->ReadCString(mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->ReadCString(mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isRefValid;
+ rv = aStream->ReadBoolean(&isRefValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsRefValid = isRefValid;
+
+ if (isRefValid) {
+ rv = aStream->ReadCString(mRef);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ }
+
+ bool isQueryValid;
+ rv = aStream->ReadBoolean(&isQueryValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsQueryValid = isQueryValid;
+
+ if (isQueryValid) {
+ rv = aStream->ReadCString(mQuery);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv;
+
+ rv = aStream->WriteBoolean(mMutable);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mScheme.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mPath.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mIsRefValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsRefValid) {
+ rv = aStream->WriteStringZ(mRef.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsQueryValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsQueryValid) {
+ rv = aStream->WriteStringZ(mQuery.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIPCSerializableURI methods:
+
+void
+nsSimpleURI::Serialize(URIParams& aParams)
+{
+ SimpleURIParams params;
+
+ params.scheme() = mScheme;
+ params.path() = mPath;
+
+ if (mIsRefValid) {
+ params.ref() = mRef;
+ } else {
+ params.ref().SetIsVoid(true);
+ }
+
+ if (mIsQueryValid) {
+ params.query() = mQuery;
+ } else {
+ params.query().SetIsVoid(true);
+ }
+
+ params.isMutable() = mMutable;
+
+ aParams = params;
+}
+
+bool
+nsSimpleURI::Deserialize(const URIParams& aParams)
+{
+ if (aParams.type() != URIParams::TSimpleURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleURIParams& params = aParams.get_SimpleURIParams();
+
+ mScheme = params.scheme();
+ mPath = params.path();
+
+ if (params.ref().IsVoid()) {
+ mRef.Truncate();
+ mIsRefValid = false;
+ } else {
+ mRef = params.ref();
+ mIsRefValid = true;
+ }
+
+ if (params.query().IsVoid()) {
+ mQuery.Truncate();
+ mIsQueryValid = false;
+ } else {
+ mQuery = params.query();
+ mIsQueryValid = true;
+ }
+
+ mMutable = params.isMutable();
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsSimpleURI::GetSpec(nsACString &result)
+{
+ if (!result.Assign(mScheme, fallible) ||
+ !result.Append(NS_LITERAL_CSTRING(":"), fallible) ||
+ !result.Append(mPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mIsQueryValid) {
+ if (!result.Append(NS_LITERAL_CSTRING("?"), fallible) ||
+ !result.Append(mQuery, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ }
+
+ if (mIsRefValid) {
+ if (!result.Append(NS_LITERAL_CSTRING("#"), fallible) ||
+ !result.Append(mRef, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ }
+
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsSimpleURI::GetSpecIgnoringRef(nsACString &result)
+{
+ result = mScheme + NS_LITERAL_CSTRING(":") + mPath;
+ if (mIsQueryValid) {
+ result += NS_LITERAL_CSTRING("?") + mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasRef(bool *result)
+{
+ *result = mIsRefValid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetSpec(const nsACString &aSpec)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredSpec;
+ net_FilterURIString(aSpec, filteredSpec);
+
+ // nsSimpleURI currently restricts the charset to US-ASCII
+ nsAutoCString spec;
+ nsresult rv = NS_EscapeURL(filteredSpec, esc_OnlyNonASCII, spec, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t colonPos = spec.FindChar(':');
+ if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos))
+ return NS_ERROR_MALFORMED_URI;
+
+ mScheme.Truncate();
+ DebugOnly<int32_t> n = spec.Left(mScheme, colonPos);
+ NS_ASSERTION(n == colonPos, "Left failed");
+ ToLowerCase(mScheme);
+
+ // This sets mPath, mQuery and mRef.
+ return SetPath(Substring(spec, colonPos + 1));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScheme(nsACString &result)
+{
+ result = mScheme;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetScheme(const nsACString &scheme)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(scheme);
+ if (!net_IsValidScheme(flat)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ mScheme = scheme;
+ ToLowerCase(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPrePath(nsACString &result)
+{
+ result = mScheme + NS_LITERAL_CSTRING(":");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUserPass(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetUserPass(const nsACString &userPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUsername(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetUsername(const nsACString &userName)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPassword(nsACString &result)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPassword(const nsACString &password)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHostPort(nsACString &result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ // Note: If this is changed, change GetAsciiHostPort as well.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHostPort(const nsACString &result)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHostAndPort(const nsACString &result)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHost(nsACString &result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetHost(const nsACString &host)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPort(int32_t *result)
+{
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPort(int32_t port)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPath(nsACString &result)
+{
+ result = mPath;
+ if (mIsQueryValid) {
+ result += NS_LITERAL_CSTRING("?") + mQuery;
+ }
+ if (mIsRefValid) {
+ result += NS_LITERAL_CSTRING("#") + mRef;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetPath(const nsACString &aPath)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString path;
+ if (!path.Assign(aPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t queryPos = path.FindChar('?');
+ int32_t hashPos = path.FindChar('#');
+
+ if (queryPos != kNotFound && hashPos != kNotFound && hashPos < queryPos) {
+ queryPos = kNotFound;
+ }
+
+ nsAutoCString query;
+ if (queryPos != kNotFound) {
+ query.Assign(Substring(path, queryPos));
+ path.Truncate(queryPos);
+ }
+
+ nsAutoCString hash;
+ if (hashPos != kNotFound) {
+ if (query.IsEmpty()) {
+ hash.Assign(Substring(path, hashPos));
+ path.Truncate(hashPos);
+ } else {
+ // We have to search the hash character in the query
+ hashPos = query.FindChar('#');
+ hash.Assign(Substring(query, hashPos));
+ query.Truncate(hashPos);
+ }
+ }
+
+ mIsQueryValid = false;
+ mQuery.Truncate();
+
+ mIsRefValid = false;
+ mRef.Truncate();
+
+ // The path
+ if (!mPath.Assign(path, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = SetQuery(query);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetRef(hash);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetRef(nsACString &result)
+{
+ if (!mIsRefValid) {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ result.Truncate();
+ } else {
+ result = mRef;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
+// string (and will result in .spec and .path having a terminal #).
+NS_IMETHODIMP
+nsSimpleURI::SetRef(const nsACString &aRef)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString ref;
+ nsresult rv = NS_EscapeURL(aRef, esc_OnlyNonASCII, ref, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ref.IsEmpty()) {
+ // Empty string means to remove ref completely.
+ mIsRefValid = false;
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ return NS_OK;
+ }
+
+ mIsRefValid = true;
+
+ // Gracefully skip initial hash
+ if (ref[0] == '#') {
+ mRef = Substring(ref, 1);
+ } else {
+ mRef = ref;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Equals(nsIURI* other, bool *result)
+{
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::EqualsExceptRef(nsIURI* other, bool *result)
+{
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+/* virtual */ nsresult
+nsSimpleURI::EqualsInternal(nsIURI* other,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ bool* result)
+{
+ NS_ENSURE_ARG_POINTER(other);
+ NS_PRECONDITION(result, "null pointer");
+
+ RefPtr<nsSimpleURI> otherUri;
+ nsresult rv = other->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = EqualsInternal(otherUri, refHandlingMode);
+ return NS_OK;
+}
+
+bool
+nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode)
+{
+ bool result = (mScheme == otherUri->mScheme &&
+ mPath == otherUri->mPath);
+
+ if (result) {
+ result = (mIsQueryValid == otherUri->mIsQueryValid &&
+ (!mIsQueryValid || mQuery == otherUri->mQuery));
+ }
+
+ if (result && refHandlingMode == eHonorRef) {
+ result = (mIsRefValid == otherUri->mIsRefValid &&
+ (!mIsRefValid || mRef == otherUri->mRef));
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SchemeIs(const char *i_Scheme, bool *o_Equals)
+{
+ NS_ENSURE_ARG_POINTER(o_Equals);
+ if (!i_Scheme) return NS_ERROR_NULL_POINTER;
+
+ const char *this_scheme = mScheme.get();
+
+ // mScheme is guaranteed to be lower case.
+ if (*i_Scheme == *this_scheme || *i_Scheme == (*this_scheme - ('a' - 'A')) ) {
+ *o_Equals = PL_strcasecmp(this_scheme, i_Scheme) ? false : true;
+ } else {
+ *o_Equals = false;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI*
+nsSimpleURI::StartClone(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ nsSimpleURI* url = new nsSimpleURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+}
+
+/* virtual */ void
+nsSimpleURI::SetRefOnClone(nsSimpleURI* url,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef)
+{
+ if (refHandlingMode == eHonorRef) {
+ url->mRef = mRef;
+ url->mIsRefValid = mIsRefValid;
+ } else if (refHandlingMode == eReplaceRef) {
+ url->SetRef(newRef);
+ }
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Clone(nsIURI** result)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::CloneIgnoringRef(nsIURI** result)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::CloneWithNewRef(const nsACString &newRef, nsIURI** result)
+{
+ return CloneInternal(eReplaceRef, newRef, result);
+}
+
+nsresult
+nsSimpleURI::CloneInternal(nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString &newRef,
+ nsIURI** result)
+{
+ RefPtr<nsSimpleURI> url = StartClone(refHandlingMode, newRef);
+ if (!url)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Note: |url| may well have mMutable false at this point, so
+ // don't call any setter methods.
+ url->mScheme = mScheme;
+ url->mPath = mPath;
+
+ url->mIsQueryValid = mIsQueryValid;
+ if (url->mIsQueryValid) {
+ url->mQuery = mQuery;
+ }
+
+ url.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ result = relativePath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiSpec(nsACString &result)
+{
+ nsAutoCString buf;
+ nsresult rv = GetSpec(buf);
+ if (NS_FAILED(rv)) return rv;
+ return NS_EscapeURL(buf, esc_OnlyNonASCII|esc_AlwaysCopy, result, fallible);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHostPort(nsACString &result)
+{
+ // XXX This behavior mimics GetHostPort.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHost(nsACString &result)
+{
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetOriginCharset(nsACString &result)
+{
+ result.Truncate();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSimpleURI::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetContractID(char * *aContractID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes.
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassID(nsCID * *aClassID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes to not call the virtual GetClassIDNoAlloc.
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kSimpleURICID;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISimpleURI
+//----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsSimpleURI::GetMutable(bool *value)
+{
+ *value = mMutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetMutable(bool value)
+{
+ NS_ENSURE_ARG(mMutable || !value);
+
+ mMutable = value;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t
+nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t
+nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIURIWithQuery
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSimpleURI::GetFilePath(nsACString& aFilePath)
+{
+ aFilePath = mPath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetFilePath(const nsACString& aFilePath)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetQuery(nsACString& aQuery)
+{
+ if (!mIsQueryValid) {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ aQuery.Truncate();
+ } else {
+ aQuery = mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SetQuery(const nsACString& aQuery)
+{
+ NS_ENSURE_STATE(mMutable);
+
+ nsAutoCString query;
+ nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ // Empty string means to remove query completely.
+ mIsQueryValid = false;
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ return NS_OK;
+ }
+
+ mIsQueryValid = true;
+
+ // Gracefully skip initial question mark
+ if (query[0] == '?') {
+ mQuery = Substring(query, 1);
+ } else {
+ mQuery = query;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h
new file mode 100644
index 000000000..29bc9b313
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleURI_h__
+#define nsSimpleURI_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIURI.h"
+#include "nsIURIWithQuery.h"
+#include "nsISerializable.h"
+#include "nsString.h"
+#include "nsIClassInfo.h"
+#include "nsIMutable.h"
+#include "nsISizeOf.h"
+#include "nsIIPCSerializableURI.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \
+{ /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \
+ 0x0b9bb0c2, \
+ 0xfee6, \
+ 0x470b, \
+ {0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19} \
+}
+
+class nsSimpleURI
+ : public nsIURIWithQuery
+ , public nsISerializable
+ , public nsIClassInfo
+ , public nsIMutable
+ , public nsISizeOf
+ , public nsIIPCSerializableURI
+{
+protected:
+ virtual ~nsSimpleURI();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURIWITHQUERY
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIMUTABLE
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ // nsSimpleURI methods:
+
+ nsSimpleURI();
+
+ // nsISizeOf
+ // Among the sub-classes that inherit (directly or indirectly) from
+ // nsSimpleURI, measurement of the following members may be added later if
+ // DMD finds it is worthwhile:
+ // - nsJSURI: mBaseURI
+ // - nsSimpleNestedURI: mInnerURI
+ // - nsBlobURI: mPrincipal
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helper to be used by inherited classes who want to test
+ // equality given an assumed nsSimpleURI. This must NOT check
+ // the passed-in other for QI to our CID.
+ bool EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode);
+
+ // Used by StartClone (and versions of StartClone in subclasses) to
+ // handle the ref in the right way for clones.
+ void SetRefOnClone(nsSimpleURI* url, RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // NOTE: This takes the refHandlingMode as an argument because
+ // nsSimpleNestedURI's specialized version needs to know how to clone
+ // its inner URI.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // Helper to share code between Clone methods.
+ virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode,
+ const nsACString &newRef,
+ nsIURI** clone);
+
+ nsCString mScheme;
+ nsCString mPath; // NOTE: mPath does not include ref, as an optimization
+ nsCString mRef; // so that URIs with different refs can share string data.
+ nsCString mQuery; // so that URLs with different querys can share string data.
+ bool mMutable;
+ bool mIsRefValid; // To distinguish between empty-ref and no-ref.
+ bool mIsQueryValid; // To distinguish between empty-query and no-query.
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSimpleURI_h__
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
new file mode 100644
index 000000000..1bfd1fc91
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+#include "nsIOService.h"
+#include "nsStreamUtils.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetAddr.h"
+#include "nsTransportUtils.h"
+#include "nsProxyInfo.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "plstr.h"
+#include "prerr.h"
+#include "NetworkActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsThreadUtils.h"
+#include "nsISocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsISSLSocketControl.h"
+#include "nsIPipe.h"
+#include "nsIClassInfoImpl.h"
+#include "nsURLHelper.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsICancelable.h"
+#include <algorithm>
+
+#include "nsPrintfCString.h"
+#include "xpcpublic.h"
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#include "ShutdownLayer.h"
+#endif
+
+/* Following inclusions required for keepalive config not supported by NSPR. */
+#include "private/pprio.h"
+#if defined(XP_WIN)
+#include <winsock2.h>
+#include <mstcpip.h>
+#elif defined(XP_UNIX)
+#include <errno.h>
+#include <netinet/tcp.h>
+#endif
+/* End keepalive config inclusions. */
+
+#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
+#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
+#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
+#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kSocketProviderServiceCID, NS_SOCKETPROVIDERSERVICE_CID);
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketEvent : public Runnable
+{
+public:
+ nsSocketEvent(nsSocketTransport *transport, uint32_t type,
+ nsresult status = NS_OK, nsISupports *param = nullptr)
+ : mTransport(transport)
+ , mType(type)
+ , mStatus(status)
+ , mParam(param)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mTransport->OnSocketEvent(mType, mStatus, mParam);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+};
+
+//-----------------------------------------------------------------------------
+
+//#define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+#include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code)
+{
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX/2) {
+ struct {
+ PRErrorCode err_code;
+ const char *err_name;
+ }
+ errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ { PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR" },
+ { PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR" },
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ { PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR" },
+ };
+ n = n % (sizeof(errors)/sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult
+ErrorAccordingToNSPR(PRErrorCode errorCode)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ default:
+ if (psm::IsNSSErrorCode(errorCode)) {
+ rv = psm::GetXPCOMFromNSSError(errorCode);
+ }
+ break;
+
+ // NSPR's socket code can return these, but they're not worth breaking out
+ // into their own error codes, distinct from NS_ERROR_FAILURE:
+ //
+ // PR_BAD_DESCRIPTOR_ERROR
+ // PR_INVALID_ARGUMENT_ERROR
+ // PR_NOT_SOCKET_ERROR
+ // PR_NOT_TCP_SOCKET_ERROR
+ // These would indicate a bug internal to the component.
+ //
+ // PR_PROTOCOL_NOT_SUPPORTED_ERROR
+ // This means that we can't use the given "protocol" (like
+ // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
+ // above, this indicates an internal bug.
+ //
+ // PR_IS_CONNECTED_ERROR
+ // This indicates that we've applied a system call like 'bind' or
+ // 'connect' to a socket that is already connected. The socket
+ // components manage each file descriptor's state, and in some cases
+ // handle this error result internally. We shouldn't be returning
+ // this to our callers.
+ //
+ // PR_IO_ERROR
+ // This is so vague that NS_ERROR_FAILURE is just as good.
+ }
+ SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%x]\n", errorCode, rv));
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// socket input stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketInputStream::nsSocketInputStream(nsSocketTransport *trans)
+ : mTransport(trans)
+ , mReaderRefCnt(0)
+ , mCondition(NS_OK)
+ , mCallbackFlags(0)
+ , mByteCount(0)
+{
+}
+
+nsSocketInputStream::~nsSocketInputStream()
+{
+}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void
+nsSocketInputStream::OnSocketReady(nsresult condition)
+{
+ SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%x]\n",
+ this, condition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = mCallback;
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback)
+ callback->OnInputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketInputStream,
+ nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::AddRef()
+{
+ ++mReaderRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::Release()
+{
+ if (--mReaderRefCnt == 0)
+ Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Available(uint64_t *avail)
+{
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
+
+ *avail = 0;
+
+ PRFileDesc *fd;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_OK;
+ }
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Available(fd);
+
+ // PSM does not implement PR_Available() so do a best approximation of it
+ // with MSG_PEEK
+ if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
+ char c;
+
+ n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p] "
+ "using PEEK backup n=%d]\n", this, n));
+ }
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n >= 0)
+ *avail = n;
+ else {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_OK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0)
+ mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countRead = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0)
+ mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool *nonblocking)
+{
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition))
+ rv = mCondition = reason;
+ else
+ rv = NS_OK;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnInputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::AsyncWait(nsIInputStreamCallback *callback,
+ uint32_t flags,
+ uint32_t amount,
+ nsIEventTarget *target)
+{
+ SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
+
+ bool hasError = false;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewInputStreamReadyEvent(callback, target);
+ }
+ else
+ mCallback = callback;
+ mCallbackFlags = flags;
+
+ hasError = NS_FAILED(mCondition);
+ } // unlock mTransport->mLock
+
+ if (hasError) {
+ // OnSocketEvent will call OnInputStreamReady with an error code after
+ // going through the event loop. We do this because most socket callers
+ // do not expect AsyncWait() to synchronously execute the OnInputStreamReady
+ // callback.
+ mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
+ } else {
+ mTransport->OnInputPending();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket output stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport *trans)
+ : mTransport(trans)
+ , mWriterRefCnt(0)
+ , mCondition(NS_OK)
+ , mCallbackFlags(0)
+ , mByteCount(0)
+{
+}
+
+nsSocketOutputStream::~nsSocketOutputStream()
+{
+}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void
+nsSocketOutputStream::OnSocketReady(nsresult condition)
+{
+ SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%x]\n",
+ this, condition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = mCallback;
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback)
+ callback->OnOutputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream,
+ nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::AddRef()
+{
+ ++mWriterRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::Release()
+{
+ if (--mWriterRefCnt == 0)
+ Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Write(const char *buf, uint32_t count, uint32_t *countWritten)
+{
+ SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+
+ // A write of 0 bytes can be used to force the initial SSL handshake, so do
+ // not reject that.
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0)
+ mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countWritten = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR)
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnOutputClosed(rv);
+
+ // only send this notification if we have indeed written some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0)
+ mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsSocketOutputStream::WriteFromSegments(nsIInputStream *input,
+ void *closure,
+ const char *fromSegment,
+ uint32_t offset,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ nsSocketOutputStream *self = (nsSocketOutputStream *) closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteFrom(nsIInputStream *stream, uint32_t count, uint32_t *countRead)
+{
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::IsNonBlocking(bool *nonblocking)
+{
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::CloseWithStatus(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition))
+ rv = mCondition = reason;
+ else
+ rv = NS_OK;
+ }
+ if (NS_FAILED(rv))
+ mTransport->OnOutputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback *callback,
+ uint32_t flags,
+ uint32_t amount,
+ nsIEventTarget *target)
+{
+ SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this));
+
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewOutputStreamReadyEvent(callback, target);
+ }
+ else
+ mCallback = callback;
+
+ mCallbackFlags = flags;
+ }
+ mTransport->OnOutputPending();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket transport impl
+//-----------------------------------------------------------------------------
+
+nsSocketTransport::nsSocketTransport()
+ : mTypes(nullptr)
+ , mTypeCount(0)
+ , mPort(0)
+ , mProxyPort(0)
+ , mOriginPort(0)
+ , mProxyTransparent(false)
+ , mProxyTransparentResolvesHost(false)
+ , mHttpsProxy(false)
+ , mConnectionFlags(0)
+ , mState(STATE_CLOSED)
+ , mAttached(false)
+ , mInputClosed(true)
+ , mOutputClosed(true)
+ , mResolving(false)
+ , mNetAddrIsSet(false)
+ , mSelfAddrIsSet(false)
+ , mNetAddrPreResolved(false)
+ , mLock("nsSocketTransport.mLock")
+ , mFD(this)
+ , mFDref(0)
+ , mFDconnected(false)
+ , mSocketTransportService(gSocketTransportService)
+ , mInput(this)
+ , mOutput(this)
+ , mQoSBits(0x00)
+ , mKeepaliveEnabled(false)
+ , mKeepaliveIdleTimeS(-1)
+ , mKeepaliveRetryIntervalS(-1)
+ , mKeepaliveProbeCount(-1)
+ , mDoNotRetryToConnect(false)
+{
+ SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
+
+ mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
+ mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
+}
+
+nsSocketTransport::~nsSocketTransport()
+{
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+
+ CleanupTypes();
+}
+
+void
+nsSocketTransport::CleanupTypes()
+{
+ // cleanup socket type info
+ if (mTypes) {
+ for (uint32_t i = 0; i < mTypeCount; ++i) {
+ PL_strfree(mTypes[i]);
+ }
+ free(mTypes);
+ mTypes = nullptr;
+ }
+ mTypeCount = 0;
+}
+
+nsresult
+nsSocketTransport::Init(const char **types, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *givenProxyInfo)
+{
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ if (proxyInfo) {
+ mHttpsProxy = proxyInfo->IsHTTPS();
+ }
+
+ const char *proxyType = nullptr;
+ mProxyInfo = proxyInfo;
+ if (proxyInfo) {
+ mProxyPort = proxyInfo->Port();
+ mProxyHost = proxyInfo->Host();
+ // grab proxy type (looking for "socks" for example)
+ proxyType = proxyInfo->Type();
+ if (proxyType && (proxyInfo->IsHTTP() ||
+ proxyInfo->IsHTTPS() ||
+ proxyInfo->IsDirect() ||
+ !strcmp(proxyType, "unknown"))) {
+ proxyType = nullptr;
+ }
+ }
+
+ SOCKET_LOG(("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d proxy=%s:%hu]\n",
+ this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
+ mProxyHost.get(), mProxyPort));
+
+ // include proxy type as a socket type if proxy type is not "http"
+ mTypeCount = typeCount + (proxyType != nullptr);
+ if (!mTypeCount)
+ return NS_OK;
+
+ // if we have socket types, then the socket provider service had
+ // better exist!
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(kSocketProviderServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ mTypes = (char **) malloc(mTypeCount * sizeof(char *));
+ if (!mTypes)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // now verify that each socket type has a registered socket provider.
+ for (uint32_t i = 0, type = 0; i < mTypeCount; ++i) {
+ // store socket types
+ if (i == 0 && proxyType)
+ mTypes[i] = PL_strdup(proxyType);
+ else
+ mTypes[i] = PL_strdup(types[type++]);
+
+ if (!mTypes[i]) {
+ mTypeCount = i;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsCOMPtr<nsISocketProvider> provider;
+ rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("no registered socket provider");
+ return rv;
+ }
+
+ // note if socket type corresponds to a transparent proxy
+ // XXX don't hardcode SOCKS here (use proxy info's flags instead).
+ if ((strcmp(mTypes[i], "socks") == 0) ||
+ (strcmp(mTypes[i], "socks4") == 0)) {
+ mProxyTransparent = true;
+
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ // we want the SOCKS layer to send the hostname
+ // and port to the proxy and let it do the DNS.
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ const mozilla::net::NetAddr* addr)
+{
+ nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mNetAddr = *addr;
+ mNetAddrPreResolved = true;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::InitWithFilename(const char *filename)
+{
+#if defined(XP_UNIX)
+ size_t filenameLength = strlen(filename);
+
+ if (filenameLength > sizeof(mNetAddr.local.path) - 1)
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+
+ mHost.Assign(filename);
+ mPort = 0;
+ mTypeCount = 0;
+
+ mNetAddr.local.family = AF_LOCAL;
+ memcpy(mNetAddr.local.path, filename, filenameLength);
+ mNetAddr.local.path[filenameLength] = '\0';
+ mNetAddrIsSet = true;
+
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+nsresult
+nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+
+ char buf[kNetAddrMaxCStrBufSize];
+ NetAddrToString(addr, buf, sizeof(buf));
+ mHost.Assign(buf);
+
+ uint16_t port;
+ if (addr->raw.family == AF_INET)
+ port = addr->inet.port;
+ else if (addr->raw.family == AF_INET6)
+ port = addr->inet6.port;
+ else
+ port = 0;
+ mPort = ntohs(port);
+
+ memcpy(&mNetAddr, addr, sizeof(NetAddr));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ mState = STATE_TRANSFERRING;
+ SetSocketName(fd);
+ mNetAddrIsSet = true;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = 1;
+ }
+
+ // make sure new socket is non-blocking
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(fd, &opt);
+
+ SOCKET_LOG(("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
+ this, mHost.get(), mPort));
+
+ // jump to InitiateSocket to get ourselves attached to the STS poll list.
+ return PostEvent(MSG_RETRY_INIT_SOCKET);
+}
+
+nsresult
+nsSocketTransport::InitWithConnectedSocket(PRFileDesc* aFD,
+ const NetAddr* aAddr,
+ nsISupports* aSecInfo)
+{
+ mSecInfo = aSecInfo;
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult
+nsSocketTransport::PostEvent(uint32_t type, nsresult status, nsISupports *param)
+{
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%x param=%p]\n",
+ this, type, status, param));
+
+ nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+nsSocketTransport::SendStatus(nsresult status)
+{
+ SOCKET_LOG(("nsSocketTransport::SendStatus [this=%p status=%x]\n", this, status));
+
+ nsCOMPtr<nsITransportEventSink> sink;
+ uint64_t progress;
+ {
+ MutexAutoLock lock(mLock);
+ sink = mEventSink;
+ switch (status) {
+ case NS_NET_STATUS_SENDING_TO:
+ progress = mOutput.ByteCount();
+ break;
+ case NS_NET_STATUS_RECEIVING_FROM:
+ progress = mInput.ByteCount();
+ break;
+ default:
+ progress = 0;
+ break;
+ }
+ }
+ if (sink) {
+ sink->OnTransportStatus(this, status, progress, -1);
+ }
+}
+
+nsresult
+nsSocketTransport::ResolveHost()
+{
+ SOCKET_LOG(("nsSocketTransport::ResolveHost [this=%p %s:%d%s]\n",
+ this, SocketHost().get(), SocketPort(),
+ mConnectionFlags & nsSocketTransport::BYPASS_CACHE ?
+ " bypass cache" : ""));
+
+ nsresult rv;
+
+ if (mNetAddrPreResolved) {
+ mState = STATE_RESOLVING;
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+
+ if (!mProxyHost.IsEmpty()) {
+ if (!mProxyTransparent || mProxyTransparentResolvesHost) {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with proxies");
+#endif
+ // When not resolving mHost locally, we still want to ensure that
+ // it only contains valid characters. See bug 304904 for details.
+ // Sometimes the end host is not yet known and mHost is *
+ if (!net_IsValidHostName(mHost) &&
+ !mHost.Equals(NS_LITERAL_CSTRING("*"))) {
+ SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mState = STATE_RESOLVING;
+ mNetAddr.raw.family = AF_INET;
+ mNetAddr.inet.port = htons(SocketPort());
+ mNetAddr.inet.ip = htonl(INADDR_ANY);
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ mResolving = true;
+
+ uint32_t dnsFlags = 0;
+ if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4)
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ SendStatus(NS_NET_STATUS_RESOLVING_HOST);
+
+ if (!SocketHost().Equals(mOriginHost)) {
+ SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n",
+ this, mOriginHost.get(), SocketHost().get()));
+ }
+ rv = dns->AsyncResolveExtended(SocketHost(), dnsFlags, mNetworkInterfaceId, this,
+ nullptr, getter_AddRefs(mDNSRequest));
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
+ mState = STATE_RESOLVING;
+ }
+ return rv;
+}
+
+nsresult
+nsSocketTransport::BuildSocket(PRFileDesc *&fd, bool &proxyTransparent, bool &usingSSL)
+{
+ SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
+
+ nsresult rv;
+
+ proxyTransparent = false;
+ usingSSL = false;
+
+ if (mTypeCount == 0) {
+ fd = PR_OpenTCPSocket(mNetAddr.raw.family);
+ rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with socket types");
+#endif
+
+ fd = nullptr;
+
+ nsCOMPtr<nsISocketProviderService> spserv =
+ do_GetService(kSocketProviderServiceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // by setting host to mOriginHost, instead of mHost we send the
+ // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
+ // on an explicit alternate service host name
+ const char *host = mOriginHost.get();
+ int32_t port = (int32_t) mOriginPort;
+ nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
+ uint32_t controlFlags = 0;
+
+ uint32_t i;
+ for (i=0; i<mTypeCount; ++i) {
+ nsCOMPtr<nsISocketProvider> provider;
+
+ SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i]));
+
+ rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider));
+ if (NS_FAILED(rv))
+ break;
+
+ if (mProxyTransparentResolvesHost)
+ controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
+
+ if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT)
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+
+ if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE)
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+
+ if (mConnectionFlags & nsISocketTransport::MITM_OK)
+ controlFlags |= nsISocketProvider::MITM_OK;
+
+ if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE)
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+
+ nsCOMPtr<nsISupports> secinfo;
+ if (i == 0) {
+ // if this is the first type, we'll want the
+ // service to allocate a new socket
+
+ // when https proxying we want to just connect to the proxy as if
+ // it were the end host (i.e. expect the proxy's cert)
+
+ rv = provider->NewSocket(mNetAddr.raw.family,
+ mHttpsProxy ? mProxyHost.get() : host,
+ mHttpsProxy ? mProxyPort : port,
+ proxyInfo, mOriginAttributes,
+ controlFlags, &fd,
+ getter_AddRefs(secinfo));
+
+ if (NS_SUCCEEDED(rv) && !fd) {
+ NS_NOTREACHED("NewSocket succeeded but failed to create a PRFileDesc");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else {
+ // the socket has already been allocated,
+ // so we just want the service to add itself
+ // to the stack (such as pushing an io layer)
+ rv = provider->AddToSocket(mNetAddr.raw.family,
+ host, port, proxyInfo,
+ mOriginAttributes, controlFlags, fd,
+ getter_AddRefs(secinfo));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv))
+ break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket info
+ bool isSSL = (strcmp(mTypes[i], "ssl") == 0);
+ if (isSSL || (strcmp(mTypes[i], "starttls") == 0)) {
+ // remember security info and give notification callbacks to PSM...
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ mSecInfo = secinfo;
+ callbacks = mCallbacks;
+ SOCKET_LOG((" [secinfo=%x callbacks=%x]\n", mSecInfo.get(), mCallbacks.get()));
+ }
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(callbacks);
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ }
+ else if ((strcmp(mTypes[i], "socks") == 0) ||
+ (strcmp(mTypes[i], "socks4") == 0)) {
+ // since socks is transparent, any layers above
+ // it do not have to worry about proxy stuff
+ proxyInfo = nullptr;
+ proxyTransparent = true;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" error pushing io layer [%u:%s rv=%x]\n", i, mTypes[i], rv));
+ if (fd) {
+ CloseSocket(fd,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsSocketTransport::InitiateSocket()
+{
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (!isLocal)
+ return NS_ERROR_OFFLINE;
+ } else if (!isLocal) {
+
+#ifdef DEBUG
+ // all IP networking has to be done from the parent
+ if (NS_SUCCEEDED(mCondition) &&
+ ((mNetAddr.raw.family == AF_INET) || (mNetAddr.raw.family == AF_INET6))) {
+ MOZ_ASSERT(!IsNeckoChild());
+ }
+#endif
+
+ if (NS_SUCCEEDED(mCondition) &&
+ xpc::AreNonLocalConnectionsDisabled() &&
+ !(IsIPAddrAny(&mNetAddr) || IsIPAddrLocal(&mNetAddr))) {
+ nsAutoCString ipaddr;
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
+ netaddr->GetAddress(ipaddr);
+ fprintf_stderr(stderr,
+ "FATAL ERROR: Non-local network connections are disabled and a connection "
+ "attempt to %s (%s) was made.\nYou should only access hostnames "
+ "available via the test networking proxy (if running mochitests) "
+ "or from a test-specific httpd.js server (if running xpcshell tests). "
+ "Browser services should be disabled or redirected to a local server.\n",
+ mHost.get(), ipaddr.get());
+ MOZ_CRASH("Attempting to connect to non-local address!");
+ }
+ }
+
+ // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
+ IsIPAddrLocal(&mNetAddr)) {
+ if (SOCKET_LOG_ENABLED()) {
+ nsAutoCString netAddrCString;
+ netAddrCString.SetCapacity(kIPv6CStrBufSize);
+ if (!NetAddrToString(&mNetAddr,
+ netAddrCString.BeginWriting(),
+ kIPv6CStrBufSize))
+ netAddrCString = NS_LITERAL_CSTRING("<IP-to-string failed>");
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket skipping "
+ "speculative connection for host [%s:%d] proxy "
+ "[%s:%d] with Local IP address [%s]",
+ mHost.get(), mPort, mProxyHost.get(), mProxyPort,
+ netAddrCString.get()));
+ }
+ mCondition = NS_ERROR_CONNECTION_REFUSED;
+ OnSocketDetached(nullptr);
+ return mCondition;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!mSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
+ if (!event)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return mSocketTransportService->NotifyWhenCanAttachSocket(event);
+ }
+
+ //
+ // if we already have a connected socket, then just attach and return.
+ //
+ if (mFD.IsInitialized()) {
+ rv = mSocketTransportService->AttachSocket(mFD, this);
+ if (NS_SUCCEEDED(rv))
+ mAttached = true;
+ return rv;
+ }
+
+ //
+ // create new socket fd, push io layers, etc.
+ //
+ PRFileDesc *fd;
+ bool proxyTransparent;
+ bool usingSSL;
+
+ rv = BuildSocket(fd, proxyTransparent, usingSSL);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" BuildSocket failed [rv=%x]\n", rv));
+ return rv;
+ }
+
+ // Attach network activity monitor
+ NetworkActivityMonitor::AttachIOLayer(fd);
+
+ PRStatus status;
+
+ // Make the socket non-blocking...
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ status = PR_SetSocketOption(fd, &opt);
+ NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
+
+ // disable the nagle algorithm - if we rely on it to coalesce writes into
+ // full packets the final packet of a multi segment POST/PUT or pipeline
+ // sequence is delayed a full rtt
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = true;
+ PR_SetSocketOption(fd, &opt);
+
+ // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
+ // The Windows default of 8KB is too small and as of vista sp1, autotuning
+ // only applies to receive window
+ int32_t sndBufferSize;
+ mSocketTransportService->GetSendBufferSize(&sndBufferSize);
+ if (sndBufferSize > 0) {
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = sndBufferSize;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+ if (mQoSBits) {
+ opt.option = PR_SockOpt_IpTypeOfService;
+ opt.value.tos = mQoSBits;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+#if defined(XP_WIN)
+ // The linger is turned off by default. This is not a hard close, but
+ // closesocket should return immediately and operating system tries to send
+ // remaining data for certain, implementation specific, amount of time.
+ // https://msdn.microsoft.com/en-us/library/ms739165.aspx
+ //
+ // Turn the linger option on an set the interval to 0. This will cause hard
+ // close of the socket.
+ opt.option = PR_SockOpt_Linger;
+ opt.value.linger.polarity = 1;
+ opt.value.linger.linger = 0;
+ PR_SetSocketOption(fd, &opt);
+#endif
+
+ // inform socket transport about this newly created socket...
+ rv = mSocketTransportService->AttachSocket(fd, this);
+ if (NS_FAILED(rv)) {
+ CloseSocket(fd,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return rv;
+ }
+ mAttached = true;
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = false;
+ }
+
+ SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
+ mState = STATE_CONNECTING;
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ SendStatus(NS_NET_STATUS_CONNECTING_TO);
+
+ if (SOCKET_LOG_ENABLED()) {
+ char buf[kNetAddrMaxCStrBufSize];
+ NetAddrToString(&mNetAddr, buf, sizeof(buf));
+ SOCKET_LOG((" trying address: %s\n", buf));
+ }
+
+ //
+ // Initiate the connect() to the host...
+ //
+ PRNetAddr prAddr;
+ {
+ if (mBindAddr) {
+ MutexAutoLock lock(mLock);
+ NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
+ status = PR_Bind(fd, &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ mBindAddr = nullptr;
+ }
+ }
+
+ NetAddrToPRNetAddr(&mNetAddr, &prAddr);
+
+#ifdef XP_WIN
+ // Find the real tcp socket and set non-blocking once again!
+ // Bug 1158189.
+ PRFileDesc *bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
+ if (bottom) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
+ u_long nonblocking = 1;
+ if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
+ NS_WARNING("Socket could not be set non-blocking!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+ }
+ else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the PR_Connect(...) would block, then poll for a connection.
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code))
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ //
+ // If the socket is already connected, then return success...
+ //
+ else if (PR_IS_CONNECTED_ERROR == code) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mSecInfo && !mProxyHost.IsEmpty() && proxyTransparent && usingSSL) {
+ // if the connection phase is finished, and the ssl layer has
+ // been pushed, and we were proxying (transparently; ie. nothing
+ // has to happen in the protocol layer above us), it's time for
+ // the ssl to start doing it's thing.
+ nsCOMPtr<nsISSLSocketControl> secCtrl =
+ do_QueryInterface(mSecInfo);
+ if (secCtrl) {
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ secCtrl->ProxyStartSSL();
+ }
+ // XXX what if we were forced to poll on the socket for a successful
+ // connection... wouldn't we need to call ProxyStartSSL after a call
+ // to PR_ConnectContinue indicates that we are connected?
+ //
+ // XXX this appears to be what the old socket transport did. why
+ // isn't this broken?
+ }
+ }
+ //
+ // A SOCKS request was rejected; get the actual error code from
+ // the OS error
+ //
+ else if (PR_UNKNOWN_ERROR == code &&
+ mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ rv = ErrorAccordingToNSPR(code);
+ }
+ //
+ // The connection was refused...
+ //
+ else {
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
+ }
+
+ rv = ErrorAccordingToNSPR(code);
+ if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty())
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ }
+ return rv;
+}
+
+bool
+nsSocketTransport::RecoverFromError()
+{
+ NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
+
+ SOCKET_LOG(("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%x]\n",
+ this, mState, mCondition));
+
+ if (mDoNotRetryToConnect) {
+ SOCKET_LOG(("nsSocketTransport::RecoverFromError do not retry because "
+ "mDoNotRetryToConnect is set [this=%p]\n",
+ this));
+ return false;
+ }
+
+#if defined(XP_UNIX)
+ // Unix domain connections don't have multiple addresses to try,
+ // so the recovery techniques here don't apply.
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL)
+ return false;
+#endif
+
+ // can only recover from errors in these states
+ if (mState != STATE_RESOLVING && mState != STATE_CONNECTING)
+ return false;
+
+ nsresult rv;
+
+ // OK to check this outside mLock
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ // can only recover from these errors
+ if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_NET_TIMEOUT &&
+ mCondition != NS_ERROR_UNKNOWN_HOST &&
+ mCondition != NS_ERROR_UNKNOWN_PROXY_HOST)
+ return false;
+
+ bool tryAgain = false;
+
+ if ((mState == STATE_CONNECTING) && mDNSRecord &&
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ if (mNetAddr.raw.family == AF_INET) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+
+ if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) &&
+ mCondition == NS_ERROR_UNKNOWN_HOST &&
+ mState == STATE_RESOLVING &&
+ !mProxyTransparentResolvesHost) {
+ SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n"));
+ mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4);
+ tryAgain = true;
+ }
+
+ // try next ip address only if past the resolver stage...
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" trying again with next ip address\n"));
+ tryAgain = true;
+ }
+ else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) {
+ // Drop state to closed. This will trigger new round of DNS
+ // resolving bellow.
+ // XXX Could be optimized to only switch the flags to save duplicate
+ // connection attempts.
+ SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts,"
+ " trying lookup/connect again with both ipv4/ipv6\n"));
+ mState = STATE_CLOSED;
+ mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4);
+ tryAgain = true;
+ }
+ }
+
+ // prepare to try again.
+ if (tryAgain) {
+ uint32_t msg;
+
+ if (mState == STATE_CONNECTING) {
+ mState = STATE_RESOLVING;
+ msg = MSG_DNS_LOOKUP_COMPLETE;
+ }
+ else {
+ mState = STATE_CLOSED;
+ msg = MSG_ENSURE_CONNECT;
+ }
+
+ rv = PostEvent(msg, NS_OK);
+ if (NS_FAILED(rv))
+ tryAgain = false;
+ }
+
+ return tryAgain;
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnMsgInputClosed(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%x]\n",
+ this, reason));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ mInputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ else if (mOutputClosed)
+ mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ else {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags &= ~PR_POLL_READ;
+ mInput.OnSocketReady(reason);
+ }
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnMsgOutputClosed(nsresult reason)
+{
+ SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%x]\n",
+ this, reason));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ mOutputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ else if (mInputClosed)
+ mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ else {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput.OnSocketReady(reason);
+ }
+}
+
+void
+nsSocketTransport::OnSocketConnected()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ mState = STATE_TRANSFERRING;
+
+ // Set the m*AddrIsSet flags only when state has reached TRANSFERRING
+ // because we need to make sure its value does not change due to failover
+ mNetAddrIsSet = true;
+
+ // assign mFD (must do this within the transport lock), but take care not
+ // to trample over mFDref if mFD is already set.
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(mFD.IsInitialized(), "no socket");
+ NS_ASSERTION(mFDref == 1, "wrong socket ref count");
+ SetSocketName(mFD);
+ mFDconnected = true;
+
+#ifdef XP_WIN
+ if (!IsWin2003OrLater()) { // windows xp
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) {
+ SOCKET_LOG(("%p checking rwin on xp originally=%u\n",
+ this, opt.value.recv_buffer_size));
+ if (opt.value.recv_buffer_size < 65535) {
+ opt.value.recv_buffer_size = 65535;
+ PR_SetSocketOption(mFD, &opt);
+ }
+ }
+ }
+#endif
+ }
+
+ // Ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv));
+ }
+ }
+
+ SendStatus(NS_NET_STATUS_CONNECTED_TO);
+}
+
+void
+nsSocketTransport::SetSocketName(PRFileDesc *fd)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (mSelfAddrIsSet) {
+ return;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) {
+ PRNetAddrToNetAddr(&prAddr, &mSelfAddr);
+ mSelfAddrIsSet = true;
+ }
+}
+
+PRFileDesc *
+nsSocketTransport::GetFD_Locked()
+{
+ mLock.AssertCurrentThreadOwns();
+
+ // mFD is not available to the streams while disconnected.
+ if (!mFDconnected)
+ return nullptr;
+
+ if (mFD.IsInitialized())
+ mFDref++;
+
+ return mFD;
+}
+
+class ThunkPRClose : public Runnable
+{
+public:
+ explicit ThunkPRClose(PRFileDesc *fd) : mFD(fd) {}
+
+ NS_IMETHOD Run() override
+ {
+ nsSocketTransport::CloseSocket(mFD,
+ gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return NS_OK;
+ }
+private:
+ PRFileDesc *mFD;
+};
+
+void
+STS_PRCloseOnSocketTransport(PRFileDesc *fd)
+{
+ if (gSocketTransportService) {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ // FIX - Should use RUN_ON_THREAD once it's generally available
+ // RUN_ON_THREAD(gSocketThread,WrapRunnableNM(&PR_Close, mFD);
+ gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
+ } else {
+ // something horrible has happened
+ NS_ASSERTION(gSocketTransportService, "No STS service");
+ }
+}
+
+void
+nsSocketTransport::ReleaseFD_Locked(PRFileDesc *fd)
+{
+ mLock.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mFD == fd, "wrong fd");
+ SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref));
+
+ if (--mFDref == 0) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ SOCKET_LOG(("Intentional leak"));
+ } else if (PR_GetCurrentThread() == gSocketThread) {
+ SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
+ CloseSocket(mFD,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ } else {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ STS_PRCloseOnSocketTransport(mFD);
+ }
+ mFD = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// socket event handler impl
+
+void
+nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *param)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%x param=%p]\n",
+ this, type, status, param));
+
+ if (NS_FAILED(mCondition)) {
+ // block event since we're apparently already dead.
+ SOCKET_LOG((" blocking event [condition=%x]\n", mCondition));
+ //
+ // notify input/output streams in case either has a pending notify.
+ //
+ mInput.OnSocketReady(mCondition);
+ mOutput.OnSocketReady(mCondition);
+ return;
+ }
+
+ switch (type) {
+ case MSG_ENSURE_CONNECT:
+ SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
+ //
+ // ensure that we have created a socket, attached it, and have a
+ // connection.
+ //
+ if (mState == STATE_CLOSED) {
+ // Unix domain sockets are ready to connect; mNetAddr is all we
+ // need. Internet address families require a DNS lookup (or possibly
+ // several) before we can connect.
+#if defined(XP_UNIX)
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL)
+ mCondition = InitiateSocket();
+ else
+#endif
+ mCondition = ResolveHost();
+
+ } else {
+ SOCKET_LOG((" ignoring redundant event\n"));
+ }
+ break;
+
+ case MSG_DNS_LOOKUP_COMPLETE:
+ if (mDNSRequest) // only send this if we actually resolved anything
+ SendStatus(NS_NET_STATUS_RESOLVED_HOST);
+
+ SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
+ mDNSRequest = nullptr;
+ if (param) {
+ mDNSRecord = static_cast<nsIDNSRecord *>(param);
+ mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ }
+ // status contains DNS lookup status
+ if (NS_FAILED(status)) {
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
+ !mProxyHost.IsEmpty())
+ mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
+ else
+ mCondition = status;
+ }
+ else if (mState == STATE_RESOLVING) {
+ mCondition = InitiateSocket();
+ }
+ break;
+
+ case MSG_RETRY_INIT_SOCKET:
+ mCondition = InitiateSocket();
+ break;
+
+ case MSG_INPUT_CLOSED:
+ SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
+ OnMsgInputClosed(status);
+ break;
+
+ case MSG_INPUT_PENDING:
+ SOCKET_LOG((" MSG_INPUT_PENDING\n"));
+ OnMsgInputPending();
+ break;
+
+ case MSG_OUTPUT_CLOSED:
+ SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
+ OnMsgOutputClosed(status);
+ break;
+
+ case MSG_OUTPUT_PENDING:
+ SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
+ OnMsgOutputPending();
+ break;
+ case MSG_TIMEOUT_CHANGED:
+ SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
+ mPollTimeout = mTimeouts[(mState == STATE_TRANSFERRING)
+ ? TIMEOUT_READ_WRITE : TIMEOUT_CONNECT];
+ break;
+ default:
+ SOCKET_LOG((" unhandled event!\n"));
+ }
+
+ if (NS_FAILED(mCondition)) {
+ SOCKET_LOG((" after event [this=%p cond=%x]\n", this, mCondition));
+ if (!mAttached) // need to process this error ourselves...
+ OnSocketDetached(nullptr);
+ }
+ else if (mPollFlags == PR_POLL_EXCEPT)
+ mPollFlags = 0; // make idle
+}
+
+//-----------------------------------------------------------------------------
+// socket handler impl
+
+void
+nsSocketTransport::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
+ this, outFlags));
+
+ if (outFlags == -1) {
+ SOCKET_LOG(("socket timeout expired\n"));
+ mCondition = NS_ERROR_NET_TIMEOUT;
+ return;
+ }
+
+ if (mState == STATE_TRANSFERRING) {
+ // if waiting to write and socket is writable or hit an exception.
+ if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput.OnSocketReady(NS_OK);
+ }
+ // if waiting to read and socket is readable or hit an exception.
+ if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_READ;
+ mInput.OnSocketReady(NS_OK);
+ }
+ // Update poll timeout in case it was changed
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+ else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ PRStatus status = PR_ConnectContinue(fd, outFlags);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(connectStarted,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ if (mNetAddr.raw.family == AF_INET) {
+ Telemetry::Accumulate(
+ Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ Telemetry::Accumulate(
+ Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ }
+ else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the connect is still not ready, then continue polling...
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ // Set up the select flags for connect...
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ // Update poll timeout in case it was changed
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+ //
+ // The SOCKS proxy rejected our request. Find out why.
+ //
+ else if (PR_UNKNOWN_ERROR == code &&
+ mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ else {
+ //
+ // else, the connection failed...
+ //
+ mCondition = ErrorAccordingToNSPR(code);
+ if ((mCondition == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty())
+ mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ SOCKET_LOG((" connection failed! [reason=%x]\n", mCondition));
+ }
+ }
+ }
+ else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+ SOCKET_LOG(("We are in shutdown so skip PR_ConnectContinue and set "
+ "and error.\n"));
+ mCondition = NS_ERROR_ABORT;
+ }
+ else {
+ NS_ERROR("unexpected socket state");
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (mPollFlags == PR_POLL_EXCEPT)
+ mPollFlags = 0; // make idle
+}
+
+// called on the socket thread only
+void
+nsSocketTransport::OnSocketDetached(PRFileDesc *fd)
+{
+ SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%x]\n",
+ this, mCondition));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ // if we didn't initiate this detach, then be sure to pass an error
+ // condition up to our consumers. (e.g., STS is shutting down.)
+ if (NS_SUCCEEDED(mCondition)) {
+ if (gIOService->IsOffline()) {
+ mCondition = NS_ERROR_OFFLINE;
+ }
+ else {
+ mCondition = NS_ERROR_ABORT;
+ }
+ }
+
+ // If we are not shutting down try again.
+ if (!gIOService->IsNetTearingDown() && RecoverFromError())
+ mCondition = NS_OK;
+ else {
+ mState = STATE_CLOSED;
+
+ // make sure there isn't any pending DNS request
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ //
+ // notify input/output streams
+ //
+ mInput.OnSocketReady(mCondition);
+ mOutput.OnSocketReady(mCondition);
+ }
+
+ // break any potential reference cycle between the security info object
+ // and ourselves by resetting its notification callbacks object. see
+ // bug 285991 for details.
+ nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(nullptr);
+
+ // finally, release our reference to the socket (must do this within
+ // the transport lock) possibly closing the socket. Also release our
+ // listeners to break potential refcount cycles.
+
+ // We should be careful not to release mEventSink and mCallbacks while
+ // we're locked, because releasing it might require acquiring the lock
+ // again, so we just null out mEventSink and mCallbacks while we're
+ // holding the lock, and let the stack based objects' destuctors take
+ // care of destroying it if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
+ nsCOMPtr<nsITransportEventSink> ourEventSink;
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ ReleaseFD_Locked(mFD);
+ // flag mFD as unusable; this prevents other consumers from
+ // acquiring a reference to mFD.
+ mFDconnected = false;
+ }
+
+ // We must release mCallbacks and mEventSink to avoid memory leak
+ // but only when RecoverFromError() above failed. Otherwise we lose
+ // link with UI and security callbacks on next connection attempt
+ // round. That would lead e.g. to a broken certificate exception page.
+ if (NS_FAILED(mCondition)) {
+ mCallbacks.swap(ourCallbacks);
+ mEventSink.swap(ourEventSink);
+ }
+ }
+}
+
+void
+nsSocketTransport::IsLocal(bool *aIsLocal)
+{
+ {
+ MutexAutoLock lock(mLock);
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mNetAddr.raw.family == PR_AF_LOCAL)
+ {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ *aIsLocal = IsLoopBackAddress(&mNetAddr);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransport,
+ nsISocketTransport,
+ nsITransport,
+ nsIDNSListener,
+ nsIClassInfo,
+ nsIInterfaceRequestor)
+NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport,
+ nsISocketTransport,
+ nsITransport,
+ nsIDNSListener,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsSocketTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ SOCKET_LOG(("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n",
+ this, flags));
+
+ NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ //bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ !openBlocking, true, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeIn;
+ }
+ else
+ *result = &mInput;
+
+ // flag input stream as open
+ mInputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n",
+ this, flags));
+
+ NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ //bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ true, !openBlocking, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeOut;
+ }
+ else
+ *result = &mOutput;
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ mDoNotRetryToConnect = true;
+
+ mInput.CloseWithStatus(reason);
+ mOutput.CloseWithStatus(reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityInfo(nsISupports **secinfo)
+{
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*secinfo = mSecInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks)
+{
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*callbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks)
+{
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ NS_GetCurrentThread(),
+ getter_AddRefs(threadsafeCallbacks));
+
+ nsCOMPtr<nsISupports> secinfo;
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n",
+ mSecInfo.get(), mCallbacks.get()));
+
+ secinfo = mSecInfo;
+ }
+
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl)
+ secCtrl->SetNotificationCallbacks(threadsafeCallbacks);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ nsCOMPtr<nsITransportEventSink> temp;
+ if (target) {
+ nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(temp),
+ sink, target);
+ if (NS_FAILED(rv))
+ return rv;
+ sink = temp.get();
+ }
+
+ MutexAutoLock lock(mLock);
+ mEventSink = sink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::IsAlive(bool *result)
+{
+ *result = false;
+
+ nsresult conditionWhileLocked = NS_OK;
+ PRFileDescAutoLock fd(this, &conditionWhileLocked);
+ if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
+ return NS_OK;
+ }
+
+ // XXX do some idle-time based checks??
+
+ char c;
+ int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+
+ if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR))
+ *result = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetHost(nsACString &host)
+{
+ host = SocketHost();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPort(int32_t *port)
+{
+ *port = (int32_t) SocketPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ aNetworkInterfaceId = mNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ mNetworkInterfaceId = aNetworkInterfaceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetScriptableOriginAttributes(JSContext* aCx,
+ JS::Handle<JS::Value> aOriginAttributes)
+{
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ NeckoOriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::GetOriginAttributes(NeckoOriginAttributes* aOriginAttributes)
+{
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::SetOriginAttributes(const NeckoOriginAttributes& aOriginAttributes)
+{
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPeerAddr(NetAddr *addr)
+{
+ // once we are in the connected state, mNetAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mNetAddr from any thread without being
+ // inside a critical section.
+
+ if (!mNetAddrIsSet) {
+ SOCKET_LOG(("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.", this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mNetAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSelfAddr(NetAddr *addr)
+{
+ // once we are in the connected state, mSelfAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mSelfAddr from any thread without being
+ // inside a critical section.
+
+ if (!mSelfAddrIsSet) {
+ SOCKET_LOG(("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.", this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mSelfAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Bind(NetAddr *aLocalAddr)
+{
+ NS_ENSURE_ARG(aLocalAddr);
+
+ MutexAutoLock lock(mLock);
+ if (mAttached) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBindAddr = new NetAddr();
+ memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptablePeerAddr(nsINetAddr * *addr)
+{
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetPeerAddr(&rawAddr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*addr = new nsNetAddr(&rawAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableSelfAddr(nsINetAddr * *addr)
+{
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetSelfAddr(&rawAddr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*addr = new nsNetAddr(&rawAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTimeout(uint32_t type, uint32_t *value)
+{
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ *value = (uint32_t) mTimeouts[type];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTimeout(uint32_t type, uint32_t value)
+{
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ // truncate overly large timeout values.
+ mTimeouts[type] = (uint16_t) std::min<uint32_t>(value, UINT16_MAX);
+ PostEvent(MSG_TIMEOUT_CHANGED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetQoSBits(uint8_t aQoSBits)
+{
+ // Don't do any checking here of bits. Why? Because as of RFC-4594
+ // several different Class Selector and Assured Forwarding values
+ // have been defined, but that isn't to say more won't be added later.
+ // In that case, any checking would be an impediment to interoperating
+ // with newer QoS definitions.
+
+ mQoSBits = aQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetQoSBits(uint8_t *aQoSBits)
+{
+ *aQoSBits = mQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRecvBufferSize(uint32_t *aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS)
+ *aSize = opt.value.recv_buffer_size;
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSendBufferSize(uint32_t *aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS)
+ *aSize = opt.value.send_buffer_size;
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetRecvBufferSize(uint32_t aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS)
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSendBufferSize(uint32_t aSize)
+{
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized())
+ return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS)
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ // flag host lookup complete for the benefit of the ResolveHost method.
+ mResolving = false;
+
+ nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec);
+
+ // if posting a message fails, then we should assume that the socket
+ // transport has been shutdown. this should never happen! if it does
+ // it means that the socket transport service was shutdown before the
+ // DNS service.
+ if (NS_FAILED(rv))
+ NS_WARNING("unable to post DNS lookup complete message");
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+nsSocketTransport::GetInterface(const nsIID &iid, void **result)
+{
+ if (iid.Equals(NS_GET_IID(nsIDNSRecord))) {
+ return mDNSRecord ?
+ mDNSRecord->QueryInterface(iid, result) : NS_ERROR_NO_INTERFACE;
+ }
+ return this->QueryInterface(iid, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(count, array);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+
+NS_IMETHODIMP
+nsSocketTransport::GetConnectionFlags(uint32_t *value)
+{
+ *value = mConnectionFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetConnectionFlags(uint32_t value)
+{
+ mConnectionFlags = value;
+ mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE;
+ return NS_OK;
+}
+
+void
+nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ // The global pref toggles keepalive as a system feature; it only affects
+ // an individual socket if keepalive has been specifically enabled for it.
+ // So, ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%x]",
+ aEnabled ? "enable" : "disable", rv));
+ }
+ }
+}
+
+nsresult
+nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable)
+{
+ MOZ_ASSERT(mKeepaliveIdleTimeS > 0 &&
+ mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
+ MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
+ mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
+ MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
+ mKeepaliveProbeCount <= kMaxTCPKeepCount);
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Only enable if keepalives are globally enabled, but ensure other
+ // options are set correctly on the fd.
+ bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
+ nsresult rv = fd.SetKeepaliveVals(enable,
+ mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveVals failed rv[0x%x]", rv));
+ return rv;
+ }
+ rv = fd.SetKeepaliveEnabled(enable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", rv));
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetKeepaliveEnabled(bool *aResult)
+{
+ MOZ_ASSERT(aResult);
+
+ *aResult = mKeepaliveEnabled;
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransport::EnsureKeepaliveValsAreInitialized()
+{
+ nsresult rv = NS_OK;
+ int32_t val = -1;
+ if (mKeepaliveIdleTimeS == -1) {
+ rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveIdleTimeS = val;
+ }
+ if (mKeepaliveRetryIntervalS == -1) {
+ rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveRetryIntervalS = val;
+ }
+ if (mKeepaliveProbeCount == -1) {
+ rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveEnabled(bool aEnable)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (aEnable == mKeepaliveEnabled) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.",
+ this, aEnable ? "enabled" : "disabled"));
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (aEnable) {
+ rv = EnsureKeepaliveValsAreInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled [%p] "
+ "error [0x%x] initializing keepalive vals",
+ this, rv));
+ return rv;
+ }
+ }
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] "
+ "%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
+ "globally %s.",
+ this, aEnable ? "enabled" : "disabled",
+ mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount,
+ mSocketTransportService->IsKeepaliveEnabled() ?
+ "enabled" : "disabled"));
+
+ // Set mKeepaliveEnabled here so that state is maintained; it is possible
+ // that we're in between fds, e.g. the 1st IP address failed, so we're about
+ // to retry on a 2nd from the DNS record.
+ mKeepaliveEnabled = aEnable;
+
+ rv = SetKeepaliveEnabledInternal(aEnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv));
+ return rv;
+ }
+
+ return NS_OK;
+#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime,
+ int32_t aRetryInterval)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 ||
+ kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aIdleTime == mKeepaliveIdleTimeS &&
+ aRetryInterval == mKeepaliveRetryIntervalS) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] idle time "
+ "already %ds and retry interval already %ds.",
+ this, mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS));
+ return NS_OK;
+ }
+ mKeepaliveIdleTimeS = aIdleTime;
+ mKeepaliveRetryIntervalS = aRetryInterval;
+
+ nsresult rv = NS_OK;
+ if (mKeepaliveProbeCount == -1) {
+ int32_t val = -1;
+ nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] "
+ "keepalive %s, idle time[%ds] retry interval[%ds] "
+ "packet count[%d]",
+ this, mKeepaliveEnabled ? "enabled" : "disabled",
+ mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount));
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ rv = fd.SetKeepaliveVals(mKeepaliveEnabled,
+ mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+#else
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifdef ENABLE_SOCKET_TRACING
+
+#include <stdio.h>
+#include <ctype.h>
+#include "prenv.h"
+
+static void
+DumpBytesToFile(const char *path, const char *header, const char *buf, int32_t n)
+{
+ FILE *fp = fopen(path, "a");
+
+ fprintf(fp, "\n%s [%d bytes]\n", header, n);
+
+ const unsigned char *p;
+ while (n) {
+ p = (const unsigned char *) buf;
+
+ int32_t i, row_max = std::min(16, n);
+
+ for (i = 0; i < row_max; ++i)
+ fprintf(fp, "%02x ", *p++);
+ for (i = row_max; i < 16; ++i)
+ fprintf(fp, " ");
+
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (isprint(*p))
+ fprintf(fp, "%c", *p);
+ else
+ fprintf(fp, ".");
+ }
+
+ fprintf(fp, "\n");
+ buf += row_max;
+ n -= row_max;
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+}
+
+void
+nsSocketTransport::TraceInBuf(const char *buf, int32_t n)
+{
+ char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val)
+ return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Reading from: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+void
+nsSocketTransport::TraceOutBuf(const char *buf, int32_t n)
+{
+ char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val)
+ return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Writing to: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+#endif
+
+static void LogNSPRError(const char* aPrefix, const void *aObjPtr)
+{
+#if defined(DEBUG)
+ PRErrorCode errCode = PR_GetError();
+ int errLen = PR_GetErrorTextLength();
+ nsAutoCString errStr;
+ if (errLen > 0) {
+ errStr.SetLength(errLen);
+ PR_GetErrorText(errStr.BeginWriting());
+ }
+ NS_WARNING(nsPrintfCString(
+ "%s [%p] NSPR error[0x%x] %s.",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errLen > 0 ? errStr.BeginReading() : "<no error text>").get());
+#endif
+}
+
+nsresult
+nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(bool aEnable)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
+ "Cannot enable keepalive if global pref is disabled!");
+ if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Keepalive;
+ opt.value.keep_alive = aEnable;
+ PRStatus status = PR_SetSocketOption(mFd, &opt);
+ if (NS_WARN_IF(status != PR_SUCCESS)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+ return NS_OK;
+}
+
+static void LogOSError(const char *aPrefix, const void *aObjPtr)
+{
+#if defined(DEBUG)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+#ifdef XP_WIN
+ DWORD errCode = WSAGetLastError();
+ LPVOID errMessage;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ errCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &errMessage,
+ 0, NULL);
+#else
+ int errCode = errno;
+ char *errMessage = strerror(errno);
+#endif
+ NS_WARNING(nsPrintfCString(
+ "%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errMessage ? errMessage : "<no error text>").get());
+#ifdef XP_WIN
+ LocalFree(errMessage);
+#endif
+#endif
+}
+
+/* XXX PR_SetSockOpt does not support setting keepalive values, so native
+ * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
+ * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
+ */
+
+nsresult
+nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(bool aEnabled,
+ int aIdleTime,
+ int aRetryInterval,
+ int aProbeCount)
+{
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 ||
+ kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROsfd sock = PR_FileDesc2NativeHandle(mFd);
+ if (NS_WARN_IF(sock == -1)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+#endif
+
+#if defined(XP_WIN)
+ // Windows allows idle time and retry interval to be set; NOT ping count.
+ struct tcp_keepalive keepalive_vals = {
+ (u_long)aEnabled,
+ // Windows uses msec.
+ (u_long)(aIdleTime * 1000UL),
+ (u_long)(aRetryInterval * 1000UL)
+ };
+ DWORD bytes_returned;
+ int err = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
+ sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL,
+ NULL);
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_DARWIN)
+ // Darwin uses sec; only supports idle time being set.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE,
+ &aIdleTime, sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_UNIX)
+ // Not all *nix OSes support the following setsockopt() options
+ // ... but we assume they are supported in the Android kernel;
+ // build errors will tell us if they are not.
+#if defined(ANDROID) || defined(TCP_KEEPIDLE)
+ // Idle time until first keepalive probe; interval between ack'd probes; seconds.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE,
+ &aIdleTime, sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+#if defined(ANDROID) || defined(TCP_KEEPINTVL)
+ // Interval between unack'd keepalive probes; seconds.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL,
+ &aRetryInterval, sizeof(aRetryInterval));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+#if defined(ANDROID) || defined(TCP_KEEPCNT)
+ // Number of unack'd keepalive probes before connection times out.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT,
+ &aProbeCount, sizeof(aProbeCount));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+#endif
+ return NS_OK;
+#else
+ MOZ_ASSERT(false, "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
+ "called on unsupported platform!");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+void
+nsSocketTransport::CloseSocket(PRFileDesc *aFd, bool aTelemetryEnabled)
+{
+#if defined(XP_WIN)
+ AttachShutdownLayer(aFd);
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime closeStarted;
+ if (aTelemetryEnabled) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(aFd);
+
+ if (aTelemetryEnabled) {
+ SendPRBlockingTelemetry(closeStarted,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
+ }
+}
+
+void
+nsSocketTransport::SendPRBlockingTelemetry(PRIntervalTime aStart,
+ Telemetry::ID aIDNormal,
+ Telemetry::ID aIDShutdown,
+ Telemetry::ID aIDConnectivityChange,
+ Telemetry::ID aIDLinkChange,
+ Telemetry::ID aIDOffline)
+{
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(aIDShutdown,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
+ < 60) {
+ Telemetry::Accumulate(aIDConnectivityChange,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
+ < 60) {
+ Telemetry::Accumulate(aIDLinkChange,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
+ < 60) {
+ Telemetry::Accumulate(aIDOffline,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else {
+ Telemetry::Accumulate(aIDNormal,
+ PR_IntervalToMilliseconds(now - aStart));
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h
new file mode 100644
index 000000000..7c85ccdc4
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.h
@@ -0,0 +1,471 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransport2_h__
+#define nsSocketTransport2_h__
+
+#ifdef DEBUG_darinf
+#define ENABLE_SOCKET_TRACING
+#endif
+
+#include "mozilla/Mutex.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDNSListener.h"
+#include "nsIClassInfo.h"
+#include "mozilla/net/DNS.h"
+#include "nsASocketHandler.h"
+#include "mozilla/Telemetry.h"
+
+#include "prerror.h"
+#include "nsAutoPtr.h"
+
+class nsICancelable;
+class nsIDNSRecord;
+class nsIInterfaceRequestor;
+
+//-----------------------------------------------------------------------------
+
+// after this short interval, we will return to PR_Poll
+#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsresult
+ErrorAccordingToNSPR(PRErrorCode errorCode);
+
+class nsSocketTransport;
+
+class nsSocketInputStream : public nsIAsyncInputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsSocketInputStream(nsSocketTransport *);
+ virtual ~nsSocketInputStream();
+
+ bool IsReferenced() { return mReaderRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+private:
+ nsSocketTransport *mTransport;
+ ThreadSafeAutoRefCnt mReaderRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketOutputStream : public nsIAsyncOutputStream
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit nsSocketOutputStream(nsSocketTransport *);
+ virtual ~nsSocketOutputStream();
+
+ bool IsReferenced() { return mWriterRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+private:
+ static nsresult WriteFromSegments(nsIInputStream *, void *,
+ const char *, uint32_t offset,
+ uint32_t count, uint32_t *countRead);
+
+ nsSocketTransport *mTransport;
+ ThreadSafeAutoRefCnt mWriterRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransport final : public nsASocketHandler
+ , public nsISocketTransport
+ , public nsIDNSListener
+ , public nsIClassInfo
+ , public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsSocketTransport();
+
+ // this method instructs the socket transport to open a socket of the
+ // given type(s) to the given host or proxy.
+ nsresult Init(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo);
+
+ // Alternative Init method for when the IP-address of the host
+ // has been pre-resolved using a alternative means (e.g. FlyWeb service
+ // info).
+ nsresult InitPreResolved(const char **socketTypes, uint32_t typeCount,
+ const nsACString &host, uint16_t port,
+ const nsACString &hostRoute, uint16_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ const mozilla::net::NetAddr* addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address.
+ nsresult InitWithConnectedSocket(PRFileDesc *socketFD,
+ const NetAddr *addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address, and additionally supplies security info.
+ nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD,
+ const NetAddr* aAddr,
+ nsISupports* aSecInfo);
+
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address. We can only create
+ // unlayered, simple, stream sockets.
+ nsresult InitWithFilename(const char *filename);
+
+ // nsASocketHandler methods:
+ void OnSocketReady(PRFileDesc *, int16_t outFlags) override;
+ void OnSocketDetached(PRFileDesc *) override;
+ void IsLocal(bool *aIsLocal) override;
+ void OnKeepaliveEnabledPrefChange(bool aEnabled) override final;
+
+ // called when a socket event is handled
+ void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param);
+
+ uint64_t ByteCountReceived() override { return mInput.ByteCount(); }
+ uint64_t ByteCountSent() override { return mOutput.ByteCount(); }
+ static void CloseSocket(PRFileDesc *aFd, bool aTelemetryEnabled);
+ static void SendPRBlockingTelemetry(PRIntervalTime aStart,
+ Telemetry::ID aIDNormal,
+ Telemetry::ID aIDShutdown,
+ Telemetry::ID aIDConnectivityChange,
+ Telemetry::ID aIDLinkChange,
+ Telemetry::ID aIDOffline);
+protected:
+
+ virtual ~nsSocketTransport();
+ void CleanupTypes();
+
+private:
+
+ // event types
+ enum {
+ MSG_ENSURE_CONNECT,
+ MSG_DNS_LOOKUP_COMPLETE,
+ MSG_RETRY_INIT_SOCKET,
+ MSG_TIMEOUT_CHANGED,
+ MSG_INPUT_CLOSED,
+ MSG_INPUT_PENDING,
+ MSG_OUTPUT_CLOSED,
+ MSG_OUTPUT_PENDING
+ };
+ nsresult PostEvent(uint32_t type, nsresult status = NS_OK, nsISupports *param = nullptr);
+
+ enum {
+ STATE_CLOSED,
+ STATE_IDLE,
+ STATE_RESOLVING,
+ STATE_CONNECTING,
+ STATE_TRANSFERRING
+ };
+
+ // Safer way to get and automatically release PRFileDesc objects.
+ class MOZ_STACK_CLASS PRFileDescAutoLock
+ {
+ public:
+ explicit PRFileDescAutoLock(nsSocketTransport *aSocketTransport,
+ nsresult *aConditionWhileLocked = nullptr)
+ : mSocketTransport(aSocketTransport)
+ , mFd(nullptr)
+ {
+ MOZ_ASSERT(aSocketTransport);
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (aConditionWhileLocked) {
+ *aConditionWhileLocked = mSocketTransport->mCondition;
+ if (NS_FAILED(mSocketTransport->mCondition)) {
+ return;
+ }
+ }
+ mFd = mSocketTransport->GetFD_Locked();
+ }
+ ~PRFileDescAutoLock() {
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (mFd) {
+ mSocketTransport->ReleaseFD_Locked(mFd);
+ }
+ }
+ bool IsInitialized() {
+ return mFd;
+ }
+ operator PRFileDesc*() {
+ return mFd;
+ }
+ nsresult SetKeepaliveEnabled(bool aEnable);
+ nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime,
+ int aRetryInterval, int aProbeCount);
+ private:
+ operator PRFileDescAutoLock*() { return nullptr; }
+
+ // Weak ptr to nsSocketTransport since this is a stack class only.
+ nsSocketTransport *mSocketTransport;
+ PRFileDesc *mFd;
+ };
+ friend class PRFileDescAutoLock;
+
+ class LockedPRFileDesc
+ {
+ public:
+ explicit LockedPRFileDesc(nsSocketTransport *aSocketTransport)
+ : mSocketTransport(aSocketTransport)
+ , mFd(nullptr)
+ {
+ MOZ_ASSERT(aSocketTransport);
+ }
+ ~LockedPRFileDesc() {}
+ bool IsInitialized() {
+ return mFd;
+ }
+ LockedPRFileDesc& operator=(PRFileDesc *aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ mFd = aFd;
+ return *this;
+ }
+ operator PRFileDesc*() {
+ if (mSocketTransport->mAttached) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ }
+ return mFd;
+ }
+ bool operator==(PRFileDesc *aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ return mFd == aFd;
+ }
+ private:
+ operator LockedPRFileDesc*() { return nullptr; }
+ // Weak ptr to nsSocketTransport since it owns this class.
+ nsSocketTransport *mSocketTransport;
+ PRFileDesc *mFd;
+ };
+ friend class LockedPRFileDesc;
+
+ //-------------------------------------------------------------------------
+ // these members are "set" at initialization time and are never modified
+ // afterwards. this allows them to be safely accessed from any thread.
+ //-------------------------------------------------------------------------
+
+ // socket type info:
+ char **mTypes;
+ uint32_t mTypeCount;
+ nsCString mHost;
+ nsCString mProxyHost;
+ nsCString mOriginHost;
+ uint16_t mPort;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ uint16_t mProxyPort;
+ uint16_t mOriginPort;
+ bool mProxyTransparent;
+ bool mProxyTransparentResolvesHost;
+ bool mHttpsProxy;
+ uint32_t mConnectionFlags;
+
+ // The origin attributes are used to create sockets. The first party domain
+ // will eventually be used to isolate OCSP cache and is only non-empty when
+ // "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ // carry origin attributes down to NSPR layers which are final consumers.
+ // It must be set before the socket transport is built.
+ NeckoOriginAttributes mOriginAttributes;
+
+ uint16_t SocketPort() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; }
+ const nsCString &SocketHost() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; }
+
+ //-------------------------------------------------------------------------
+ // members accessible only on the socket transport thread:
+ // (the exception being initialization/shutdown time)
+ //-------------------------------------------------------------------------
+
+ // socket state vars:
+ uint32_t mState; // STATE_??? flags
+ bool mAttached;
+ bool mInputClosed;
+ bool mOutputClosed;
+
+ // The platform-specific network interface id that this socket
+ // associated with.
+ nsCString mNetworkInterfaceId;
+
+ // this flag is used to determine if the results of a host lookup arrive
+ // recursively or not. this flag is not protected by any lock.
+ bool mResolving;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSRecord> mDNSRecord;
+
+ // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
+ // reached STATE_TRANSFERRING. It must not change after that.
+ void SetSocketName(PRFileDesc *fd);
+ NetAddr mNetAddr;
+ NetAddr mSelfAddr; // getsockname()
+ Atomic<bool, Relaxed> mNetAddrIsSet;
+ Atomic<bool, Relaxed> mSelfAddrIsSet;
+ Atomic<bool, Relaxed> mNetAddrPreResolved;
+
+ nsAutoPtr<NetAddr> mBindAddr;
+
+ // socket methods (these can only be called on the socket thread):
+
+ void SendStatus(nsresult status);
+ nsresult ResolveHost();
+ nsresult BuildSocket(PRFileDesc *&, bool &, bool &);
+ nsresult InitiateSocket();
+ bool RecoverFromError();
+
+ void OnMsgInputPending()
+ {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT);
+ }
+ void OnMsgOutputPending()
+ {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ }
+ void OnMsgInputClosed(nsresult reason);
+ void OnMsgOutputClosed(nsresult reason);
+
+ // called when the socket is connected
+ void OnSocketConnected();
+
+ //-------------------------------------------------------------------------
+ // socket input/output objects. these may be accessed on any thread with
+ // the exception of some specific methods (XXX).
+
+ Mutex mLock; // protects members in this section.
+ LockedPRFileDesc mFD;
+ nsrefcnt mFDref; // mFD is closed when mFDref goes to zero.
+ bool mFDconnected; // mFD is available to consumer when TRUE.
+
+ // A delete protector reference to gSocketTransportService held for lifetime
+ // of 'this'. Sometimes used interchangably with gSocketTransportService due
+ // to scoping.
+ RefPtr<nsSocketTransportService> mSocketTransportService;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsISupports> mSecInfo;
+
+ nsSocketInputStream mInput;
+ nsSocketOutputStream mOutput;
+
+ friend class nsSocketInputStream;
+ friend class nsSocketOutputStream;
+
+ // socket timeouts are not protected by any lock.
+ uint16_t mTimeouts[2];
+
+ // QoS setting for socket
+ uint8_t mQoSBits;
+
+ //
+ // mFD access methods: called with mLock held.
+ //
+ PRFileDesc *GetFD_Locked();
+ void ReleaseFD_Locked(PRFileDesc *fd);
+
+ //
+ // stream state changes (called outside mLock):
+ //
+ void OnInputClosed(nsresult reason)
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgInputClosed(reason);
+ else
+ PostEvent(MSG_INPUT_CLOSED, reason);
+ }
+ void OnInputPending()
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgInputPending();
+ else
+ PostEvent(MSG_INPUT_PENDING);
+ }
+ void OnOutputClosed(nsresult reason)
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgOutputClosed(reason); // XXX need to not be inside lock!
+ else
+ PostEvent(MSG_OUTPUT_CLOSED, reason);
+ }
+ void OnOutputPending()
+ {
+ // no need to post an event if called on the socket thread
+ if (PR_GetCurrentThread() == gSocketThread)
+ OnMsgOutputPending();
+ else
+ PostEvent(MSG_OUTPUT_PENDING);
+ }
+
+#ifdef ENABLE_SOCKET_TRACING
+ void TraceInBuf(const char *buf, int32_t n);
+ void TraceOutBuf(const char *buf, int32_t n);
+#endif
+
+ // Reads prefs to get default keepalive config.
+ nsresult EnsureKeepaliveValsAreInitialized();
+
+ // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals.
+ nsresult SetKeepaliveEnabledInternal(bool aEnable);
+
+ // True if keepalive has been enabled by the socket owner. Note: Keepalive
+ // must also be enabled globally for it to be enabled in TCP.
+ bool mKeepaliveEnabled;
+
+ // Keepalive config (support varies by platform).
+ int32_t mKeepaliveIdleTimeS;
+ int32_t mKeepaliveRetryIntervalS;
+ int32_t mKeepaliveProbeCount;
+
+ bool mDoNotRetryToConnect;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransport_h__
diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp
new file mode 100644
index 000000000..d2f20651e
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -0,0 +1,1627 @@
+// vim:set sw=4 sts=4 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "nsSocketTransport2.h"
+#include "NetworkActivityMonitor.h"
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+#include "nsASocketHandler.h"
+#include "nsError.h"
+#include "prnetdb.h"
+#include "prerror.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Services.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PublicSSL.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Telemetry.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "nsIWidget.h"
+#include "mozilla/dom/FlyWebService.h"
+
+#if defined(XP_WIN)
+#include "mozilla/WindowsVersion.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gSocketTransportLog("nsSocketTransport");
+LazyLogModule gUDPSocketLog("UDPSocket");
+LazyLogModule gTCPSocketLog("TCPSocket");
+
+nsSocketTransportService *gSocketTransportService = nullptr;
+Atomic<PRThread*, Relaxed> gSocketThread;
+
+#define SEND_BUFFER_PREF "network.tcp.sendbuffer"
+#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
+#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
+#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
+#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
+#define SOCKET_LIMIT_TARGET 1000U
+#define SOCKET_LIMIT_MIN 50U
+#define BLIP_INTERVAL_PREF "network.activity.blipIntervalMilliseconds"
+#define MAX_TIME_BETWEEN_TWO_POLLS "network.sts.max_time_for_events_between_two_polls"
+#define TELEMETRY_PREF "toolkit.telemetry.enabled"
+#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN "network.sts.max_time_for_pr_close_during_shutdown"
+
+#define REPAIR_POLLABLE_EVENT_TIME 10
+
+uint32_t nsSocketTransportService::gMaxCount;
+PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
+
+//-----------------------------------------------------------------------------
+// ctor/dtor (called on the main/UI thread by the service manager)
+
+nsSocketTransportService::nsSocketTransportService()
+ : mThread(nullptr)
+ , mLock("nsSocketTransportService::mLock")
+ , mInitialized(false)
+ , mShuttingDown(false)
+ , mOffline(false)
+ , mGoingOffline(false)
+ , mRawThread(nullptr)
+ , mActiveListSize(SOCKET_LIMIT_MIN)
+ , mIdleListSize(SOCKET_LIMIT_MIN)
+ , mActiveCount(0)
+ , mIdleCount(0)
+ , mSentBytesCount(0)
+ , mReceivedBytesCount(0)
+ , mSendBufferSize(0)
+ , mKeepaliveIdleTimeS(600)
+ , mKeepaliveRetryIntervalS(1)
+ , mKeepaliveProbeCount(kDefaultTCPKeepCount)
+ , mKeepaliveEnabledPref(false)
+ , mServingPendingQueue(false)
+ , mMaxTimePerPollIter(100)
+ , mTelemetryEnabledPref(false)
+ , mMaxTimeForPrClosePref(PR_SecondsToInterval(5))
+ , mSleepPhase(false)
+ , mProbedMaxCount(false)
+#if defined(XP_WIN)
+ , mPolling(false)
+#endif
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
+ mActiveList = (SocketContext *)
+ moz_xmalloc(sizeof(SocketContext) * mActiveListSize);
+ mIdleList = (SocketContext *)
+ moz_xmalloc(sizeof(SocketContext) * mIdleListSize);
+ mPollList = (PRPollDesc *)
+ moz_xmalloc(sizeof(PRPollDesc) * (mActiveListSize + 1));
+
+ NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
+ gSocketTransportService = this;
+}
+
+nsSocketTransportService::~nsSocketTransportService()
+{
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+ NS_ASSERTION(!mInitialized, "not shutdown properly");
+
+ free(mActiveList);
+ free(mIdleList);
+ free(mPollList);
+ gSocketTransportService = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// event queue (any thread)
+
+already_AddRefed<nsIThread>
+nsSocketTransportService::GetThreadSafely()
+{
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIThread> result = mThread;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchFromScript(nsIRunnable *event, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ return Dispatch(event_ref.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
+
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ nsresult rv;
+ rv = thread ? thread->Dispatch(event_ref.forget(), flags) : NS_ERROR_NOT_INITIALIZED;
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // Thread is no longer accepting events. We must have just shut it
+ // down on the main thread. Pretend we never saw it.
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::IsOnCurrentThread(bool *result)
+{
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
+ return thread->IsOnCurrentThread(result);
+}
+
+//-----------------------------------------------------------------------------
+// socket api (socket thread only)
+
+NS_IMETHODIMP
+nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable *event)
+{
+ SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (CanAttachSocket()) {
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ auto *runnable = new LinkedRunnableEvent(event);
+ mPendingSocketQueue.insertBack(runnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AttachSocket(PRFileDesc *fd, nsASocketHandler *handler)
+{
+ SOCKET_LOG(("nsSocketTransportService::AttachSocket [handler=%p]\n", handler));
+
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (!CanAttachSocket()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SocketContext sock;
+ sock.mFD = fd;
+ sock.mHandler = handler;
+ sock.mElapsedTime = 0;
+
+ nsresult rv = AddToIdleList(&sock);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(handler);
+ return rv;
+}
+
+// the number of sockets that can be attached at any given time is
+// limited. this is done because some operating systems (e.g., Win9x)
+// limit the number of sockets that can be created by an application.
+// AttachSocket will fail if the limit is exceeded. consumers should
+// call CanAttachSocket and check the result before creating a socket.
+
+bool
+nsSocketTransportService::CanAttachSocket()
+{
+ static bool reported900FDLimit = false;
+
+ uint32_t total = mActiveCount + mIdleCount;
+ bool rv = total < gMaxCount;
+
+ if (mTelemetryEnabledPref &&
+ (((total >= 900) || !rv) && !reported900FDLimit)) {
+ reported900FDLimit = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_SESSION_AT_900FD, true);
+ }
+
+ return rv;
+}
+
+nsresult
+nsSocketTransportService::DetachSocket(SocketContext *listHead, SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n", sock->mHandler));
+ MOZ_ASSERT((listHead == mActiveList) || (listHead == mIdleList),
+ "DetachSocket invalid head");
+
+ // inform the handler that this socket is going away
+ sock->mHandler->OnSocketDetached(sock->mFD);
+ mSentBytesCount += sock->mHandler->ByteCountSent();
+ mReceivedBytesCount += sock->mHandler->ByteCountReceived();
+
+ // cleanup
+ sock->mFD = nullptr;
+ NS_RELEASE(sock->mHandler);
+
+ if (listHead == mActiveList)
+ RemoveFromPollList(sock);
+ else
+ RemoveFromIdleList(sock);
+
+ // NOTE: sock is now an invalid pointer
+
+ //
+ // notify the first element on the pending socket queue...
+ //
+ nsCOMPtr<nsIRunnable> event;
+ LinkedRunnableEvent *runnable = mPendingSocketQueue.getFirst();
+ if (runnable) {
+ event = runnable->TakeEvent();
+ runnable->remove();
+ delete runnable;
+ }
+ if (event) {
+ // move event from pending queue to dispatch queue
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransportService::AddToPollList(SocketContext *sock)
+{
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mActiveList) < mActiveListSize),
+ "AddToPollList Socket Already Active");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%p]\n", sock->mHandler));
+ if (mActiveCount == mActiveListSize) {
+ SOCKET_LOG((" Active List size of %d met\n", mActiveCount));
+ if (!GrowActiveList()) {
+ NS_ERROR("too many active sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t newSocketIndex = mActiveCount;
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ newSocketIndex = ChaosMode::randomUint32LessThan(mActiveCount + 1);
+ PodMove(mActiveList + newSocketIndex + 1, mActiveList + newSocketIndex,
+ mActiveCount - newSocketIndex);
+ PodMove(mPollList + newSocketIndex + 2, mPollList + newSocketIndex + 1,
+ mActiveCount - newSocketIndex);
+ }
+ mActiveList[newSocketIndex] = *sock;
+ mActiveCount++;
+
+ mPollList[newSocketIndex + 1].fd = sock->mFD;
+ mPollList[newSocketIndex + 1].in_flags = sock->mHandler->mPollFlags;
+ mPollList[newSocketIndex + 1].out_flags = 0;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::RemoveFromPollList(SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList [handler=%p]\n", sock->mHandler));
+
+ uint32_t index = sock - mActiveList;
+ MOZ_ASSERT(index < mActiveListSize, "invalid index");
+
+ SOCKET_LOG((" index=%u mActiveCount=%u\n", index, mActiveCount));
+
+ if (index != mActiveCount-1) {
+ mActiveList[index] = mActiveList[mActiveCount-1];
+ mPollList[index+1] = mPollList[mActiveCount];
+ }
+ mActiveCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+nsresult
+nsSocketTransportService::AddToIdleList(SocketContext *sock)
+{
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mIdleList) < mIdleListSize),
+ "AddToIdlelList Socket Already Idle");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%p]\n", sock->mHandler));
+ if (mIdleCount == mIdleListSize) {
+ SOCKET_LOG((" Idle List size of %d met\n", mIdleCount));
+ if (!GrowIdleList()) {
+ NS_ERROR("too many idle sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mIdleList[mIdleCount] = *sock;
+ mIdleCount++;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::RemoveFromIdleList(SocketContext *sock)
+{
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n", sock->mHandler));
+
+ uint32_t index = sock - mIdleList;
+ NS_ASSERTION(index < mIdleListSize, "invalid index in idle list");
+
+ if (index != mIdleCount-1)
+ mIdleList[index] = mIdleList[mIdleCount-1];
+ mIdleCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+void
+nsSocketTransportService::MoveToIdleList(SocketContext *sock)
+{
+ nsresult rv = AddToIdleList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mActiveList, sock);
+ else
+ RemoveFromPollList(sock);
+}
+
+void
+nsSocketTransportService::MoveToPollList(SocketContext *sock)
+{
+ nsresult rv = AddToPollList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mIdleList, sock);
+ else
+ RemoveFromIdleList(sock);
+}
+
+bool
+nsSocketTransportService::GrowActiveList()
+{
+ int32_t toAdd = gMaxCount - mActiveListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mActiveListSize += toAdd;
+ mActiveList = (SocketContext *)
+ moz_xrealloc(mActiveList, sizeof(SocketContext) * mActiveListSize);
+ mPollList = (PRPollDesc *)
+ moz_xrealloc(mPollList, sizeof(PRPollDesc) * (mActiveListSize + 1));
+ return true;
+}
+
+bool
+nsSocketTransportService::GrowIdleList()
+{
+ int32_t toAdd = gMaxCount - mIdleListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mIdleListSize += toAdd;
+ mIdleList = (SocketContext *)
+ moz_xrealloc(mIdleList, sizeof(SocketContext) * mIdleListSize);
+ return true;
+}
+
+PRIntervalTime
+nsSocketTransportService::PollTimeout()
+{
+ if (mActiveCount == 0)
+ return NS_SOCKET_POLL_TIMEOUT;
+
+ // compute minimum time before any socket timeout expires.
+ uint32_t minR = UINT16_MAX;
+ for (uint32_t i=0; i<mActiveCount; ++i) {
+ const SocketContext &s = mActiveList[i];
+ // mPollTimeout could be less than mElapsedTime if setTimeout
+ // was called with a value smaller than mElapsedTime.
+ uint32_t r = (s.mElapsedTime < s.mHandler->mPollTimeout)
+ ? s.mHandler->mPollTimeout - s.mElapsedTime
+ : 0;
+ if (r < minR)
+ minR = r;
+ }
+ // nsASocketHandler defines UINT16_MAX as do not timeout
+ if (minR == UINT16_MAX) {
+ SOCKET_LOG(("poll timeout: none\n"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+ SOCKET_LOG(("poll timeout: %lu\n", minR));
+ return PR_SecondsToInterval(minR);
+}
+
+int32_t
+nsSocketTransportService::Poll(uint32_t *interval,
+ TimeDuration *pollDuration)
+{
+ PRPollDesc *pollList;
+ uint32_t pollCount;
+ PRIntervalTime pollTimeout;
+ *pollDuration = 0;
+
+ // If there are pending events for this thread then
+ // DoPollIteration() should service the network without blocking.
+ bool pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+
+ if (mPollList[0].fd) {
+ mPollList[0].out_flags = 0;
+ pollList = mPollList;
+ pollCount = mActiveCount + 1;
+ pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout();
+ }
+ else {
+ // no pollable event, so busy wait...
+ pollCount = mActiveCount;
+ if (pollCount)
+ pollList = &mPollList[1];
+ else
+ pollList = nullptr;
+ pollTimeout =
+ pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
+ }
+
+ PRIntervalTime ts = PR_IntervalNow();
+
+ TimeStamp pollStart;
+ if (mTelemetryEnabledPref) {
+ pollStart = TimeStamp::NowLoRes();
+ }
+
+ SOCKET_LOG((" timeout = %i milliseconds\n",
+ PR_IntervalToMilliseconds(pollTimeout)));
+ int32_t rv = PR_Poll(pollList, pollCount, pollTimeout);
+
+ PRIntervalTime passedInterval = PR_IntervalNow() - ts;
+
+ if (mTelemetryEnabledPref && !pollStart.IsNull()) {
+ *pollDuration = TimeStamp::NowLoRes() - pollStart;
+ }
+
+ SOCKET_LOG((" ...returned after %i milliseconds\n",
+ PR_IntervalToMilliseconds(passedInterval)));
+
+ *interval = PR_IntervalToSeconds(passedInterval);
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransportService,
+ nsISocketTransportService,
+ nsIRoutedSocketTransportService,
+ nsIEventTarget,
+ nsIThreadObserver,
+ nsIRunnable,
+ nsPISocketTransportService,
+ nsIObserver)
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Init()
+{
+ if (!NS_IsMainThread()) {
+ NS_ERROR("wrong thread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mInitialized)
+ return NS_OK;
+
+ if (mShuttingDown)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), this);
+ if (NS_FAILED(rv)) return rv;
+
+ {
+ MutexAutoLock lock(mLock);
+ // Install our mThread, protecting against concurrent readers
+ thread.swap(mThread);
+ }
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService) {
+ tmpPrefService->AddObserver(SEND_BUFFER_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_ENABLED_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_IDLE_TIME_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_RETRY_INTERVAL_PREF, this, false);
+ tmpPrefService->AddObserver(KEEPALIVE_PROBE_COUNT_PREF, this, false);
+ tmpPrefService->AddObserver(MAX_TIME_BETWEEN_TWO_POLLS, this, false);
+ tmpPrefService->AddObserver(TELEMETRY_PREF, this, false);
+ tmpPrefService->AddObserver(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, this, false);
+ }
+ UpdatePrefs();
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-initial-state", false);
+ obsSvc->AddObserver(this, "last-pb-context-exited", false);
+ obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Shutdown(bool aXpcomShutdown)
+{
+ SOCKET_LOG(("nsSocketTransportService::Shutdown\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized)
+ return NS_OK;
+
+ if (mShuttingDown)
+ return NS_ERROR_UNEXPECTED;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // signal the socket thread to shutdown
+ mShuttingDown = true;
+
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ }
+
+ if (!aXpcomShutdown) {
+ return ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSocketTransportService::ShutdownThread()
+{
+ SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized || !mShuttingDown)
+ return NS_OK;
+
+ // join with thread
+ mThread->Shutdown();
+ {
+ MutexAutoLock lock(mLock);
+ // Drop our reference to mThread and make sure that any concurrent
+ // readers are excluded
+ mThread = nullptr;
+ }
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService)
+ tmpPrefService->RemoveObserver(SEND_BUFFER_PREF, this);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, "profile-initial-state");
+ obsSvc->RemoveObserver(this, "last-pb-context-exited");
+ obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown-threads");
+ }
+
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+
+ NetworkActivityMonitor::Shutdown();
+
+ mInitialized = false;
+ mShuttingDown = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetOffline(bool *offline)
+{
+ *offline = mOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::SetOffline(bool offline)
+{
+ MutexAutoLock lock(mLock);
+ if (!mOffline && offline) {
+ // signal the socket thread to go offline, so it will detach sockets
+ mGoingOffline = true;
+ mOffline = true;
+ }
+ else if (mOffline && !offline) {
+ mOffline = false;
+ }
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveIdleTime(int32_t *aKeepaliveIdleTimeS)
+{
+ MOZ_ASSERT(aKeepaliveIdleTimeS);
+ if (NS_WARN_IF(!aKeepaliveIdleTimeS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveRetryInterval(int32_t *aKeepaliveRetryIntervalS)
+{
+ MOZ_ASSERT(aKeepaliveRetryIntervalS);
+ if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveProbeCount(int32_t *aKeepaliveProbeCount)
+{
+ MOZ_ASSERT(aKeepaliveProbeCount);
+ if (NS_WARN_IF(!aKeepaliveProbeCount)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveProbeCount = mKeepaliveProbeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateTransport(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result)
+{
+ return CreateRoutedTransport(types, typeCount, host, port, NS_LITERAL_CSTRING(""), 0,
+ proxyInfo, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateRoutedTransport(const char **types,
+ uint32_t typeCount,
+ const nsACString &host,
+ int32_t port,
+ const nsACString &hostRoute,
+ int32_t portRoute,
+ nsIProxyInfo *proxyInfo,
+ nsISocketTransport **result)
+{
+ // Check FlyWeb table for host mappings. If one exists, then use that.
+ RefPtr<mozilla::dom::FlyWebService> fws =
+ mozilla::dom::FlyWebService::GetExisting();
+ if (fws) {
+ nsresult rv = fws->CreateTransportForHost(types, typeCount, host, port,
+ hostRoute, portRoute,
+ proxyInfo, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ nsresult rv = trans->Init(types, typeCount, host, port, hostRoute, portRoute, proxyInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainTransport(nsIFile *aPath,
+ nsISocketTransport **result)
+{
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+
+ rv = trans->InitWithFilename(path.get());
+ if (NS_FAILED(rv))
+ return rv;
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event.
+ if (PR_GetCurrentThread() == gSocketThread) {
+ // this check is redundant to one done inside ::Signal(), but
+ // we can do it here and skip obtaining the lock - given that
+ // this is a relatively common occurance its worth the
+ // redundant code
+ SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n"));
+ return NS_OK;
+ }
+#else
+ if (gIOService->IsNetTearingDown()) {
+ // Poll can hang sometimes. If we are in shutdown, we are going to
+ // start a watchdog. If we do not exit poll within
+ // REPAIR_POLLABLE_EVENT_TIME signal a pollable event again.
+ StartPollWatchdog();
+ }
+#endif
+
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread,
+ bool mayWait)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed)
+{
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::MarkTheLastElementOfPendingQueue()
+{
+ mServingPendingQueue = false;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Run()
+{
+ SOCKET_LOG(("STS thread init %d sockets\n", gMaxCount));
+
+ psm::InitializeSSLServerCertVerificationThreads();
+
+ gSocketThread = PR_GetCurrentThread();
+
+ {
+ MutexAutoLock lock(mLock);
+ mPollableEvent.reset(new PollableEvent());
+ //
+ // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+ // or similar software.
+ //
+ // NOTE: per bug 191739, this failure could also be caused by lack
+ // of a loopback device on Windows and OS/2 platforms (it creates
+ // a loopback socket pair on these platforms to implement a pollable
+ // event object). if we can't create a pollable event, then we'll
+ // have to "busy wait" to implement the socket event queue :-(
+ //
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ NS_WARNING("running socket transport thread without a pollable event");
+ SOCKET_LOG(("running socket transport thread without a pollable event"));
+ }
+
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+ }
+
+ mRawThread = NS_GetCurrentThread();
+
+ // hook ourselves up to observe event processing for this thread
+ nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread);
+ threadInt->SetObserver(this);
+
+ // make sure the pseudo random number generator is seeded on this thread
+ srand(static_cast<unsigned>(PR_Now()));
+
+ // For the calculation of the duration of the last cycle (i.e. the last for-loop
+ // iteration before shutdown).
+ TimeStamp startOfCycleForLastCycleCalc;
+ int numberOfPendingEventsLastCycle;
+
+ // For measuring of the poll iteration duration without time spent blocked
+ // in poll().
+ TimeStamp pollCycleStart;
+ // Time blocked in poll().
+ TimeDuration singlePollDuration;
+
+ // For calculating the time needed for a new element to run.
+ TimeStamp startOfIteration;
+ TimeStamp startOfNextIteration;
+ int numberOfPendingEvents;
+
+ // If there is too many pending events queued, we will run some poll()
+ // between them and the following variable is cumulative time spent
+ // blocking in poll().
+ TimeDuration pollDuration;
+
+ for (;;) {
+ bool pendingEvents = false;
+
+ numberOfPendingEvents = 0;
+ numberOfPendingEventsLastCycle = 0;
+ if (mTelemetryEnabledPref) {
+ startOfCycleForLastCycleCalc = TimeStamp::NowLoRes();
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ pollDuration = 0;
+
+ do {
+ if (mTelemetryEnabledPref) {
+ pollCycleStart = TimeStamp::NowLoRes();
+ }
+
+ DoPollIteration(&singlePollDuration);
+
+ if (mTelemetryEnabledPref && !pollCycleStart.IsNull()) {
+ Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME,
+ singlePollDuration.ToMilliseconds());
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_CYCLE,
+ pollCycleStart + singlePollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration += singlePollDuration;
+ }
+
+ mRawThread->HasPendingEvents(&pendingEvents);
+ if (pendingEvents) {
+ if (!mServingPendingQueue) {
+ nsresult rv = Dispatch(NewRunnableMethod(this,
+ &nsSocketTransportService::MarkTheLastElementOfPendingQueue),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not dispatch a new event on the "
+ "socket thread.");
+ } else {
+ mServingPendingQueue = true;
+ }
+
+ if (mTelemetryEnabledPref) {
+ startOfIteration = startOfNextIteration;
+ // Everything that comes after this point will
+ // be served in the next iteration. If no even
+ // arrives, startOfNextIteration will be reset at the
+ // beginning of each for-loop.
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ }
+ TimeStamp eventQueueStart = TimeStamp::NowLoRes();
+ do {
+ NS_ProcessNextEvent(mRawThread);
+ numberOfPendingEvents++;
+ pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+ } while (pendingEvents && mServingPendingQueue &&
+ ((TimeStamp::NowLoRes() -
+ eventQueueStart).ToMilliseconds() <
+ mMaxTimePerPollIter));
+
+ if (mTelemetryEnabledPref && !mServingPendingQueue &&
+ !startOfIteration.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENTS_CYCLE,
+ startOfIteration + pollDuration,
+ TimeStamp::NowLoRes());
+
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_PENDING_EVENTS,
+ numberOfPendingEvents);
+
+ numberOfPendingEventsLastCycle += numberOfPendingEvents;
+ numberOfPendingEvents = 0;
+ pollDuration = 0;
+ }
+ }
+ } while (pendingEvents);
+
+ bool goingOffline = false;
+ // now that our event queue is empty, check to see if we should exit
+ {
+ MutexAutoLock lock(mLock);
+ if (mShuttingDown) {
+ if (mTelemetryEnabledPref &&
+ !startOfCycleForLastCycleCalc.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_PENDING_EVENTS_IN_THE_LAST_CYCLE,
+ numberOfPendingEventsLastCycle);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE,
+ startOfCycleForLastCycleCalc,
+ TimeStamp::NowLoRes());
+ }
+ break;
+ }
+ if (mGoingOffline) {
+ mGoingOffline = false;
+ goingOffline = true;
+ }
+ }
+ // Avoid potential deadlock
+ if (goingOffline)
+ Reset(true);
+ }
+
+ SOCKET_LOG(("STS shutting down thread\n"));
+
+ // detach all sockets, including locals
+ Reset(false);
+
+ // Final pass over the event queue. This makes sure that events posted by
+ // socket detach handlers get processed.
+ NS_ProcessPendingEvents(mRawThread);
+
+ gSocketThread = nullptr;
+
+ psm::StopSSLServerCertVerificationThreads();
+
+ SOCKET_LOG(("STS thread exit\n"));
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::DetachSocketWithGuard(bool aGuardLocals,
+ SocketContext *socketList,
+ int32_t index)
+{
+ bool isGuarded = false;
+ if (aGuardLocals) {
+ socketList[index].mHandler->IsLocal(&isGuarded);
+ if (!isGuarded)
+ socketList[index].mHandler->KeepWhenOffline(&isGuarded);
+ }
+ if (!isGuarded)
+ DetachSocket(socketList, &socketList[index]);
+}
+
+void
+nsSocketTransportService::Reset(bool aGuardLocals)
+{
+ // detach any sockets
+ int32_t i;
+ for (i = mActiveCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mActiveList, i);
+ }
+ for (i = mIdleCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mIdleList, i);
+ }
+}
+
+nsresult
+nsSocketTransportService::DoPollIteration(TimeDuration *pollDuration)
+{
+ SOCKET_LOG(("STS poll iter\n"));
+
+ int32_t i, count;
+ //
+ // poll loop
+ //
+ // walk active list backwards to see if any sockets should actually be
+ // idle, then walk the idle list backwards to see if any idle sockets
+ // should become active. take care to check only idle sockets that
+ // were idle to begin with ;-)
+ //
+ count = mIdleCount;
+ for (i=mActiveCount-1; i>=0; --i) {
+ //---
+ SOCKET_LOG((" active [%u] { handler=%p condition=%x pollflags=%hu }\n", i,
+ mActiveList[i].mHandler,
+ mActiveList[i].mHandler->mCondition,
+ mActiveList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition))
+ DetachSocket(mActiveList, &mActiveList[i]);
+ else {
+ uint16_t in_flags = mActiveList[i].mHandler->mPollFlags;
+ if (in_flags == 0)
+ MoveToIdleList(&mActiveList[i]);
+ else {
+ // update poll flags
+ mPollList[i+1].in_flags = in_flags;
+ mPollList[i+1].out_flags = 0;
+ }
+ }
+ }
+ for (i=count-1; i>=0; --i) {
+ //---
+ SOCKET_LOG((" idle [%u] { handler=%p condition=%x pollflags=%hu }\n", i,
+ mIdleList[i].mHandler,
+ mIdleList[i].mHandler->mCondition,
+ mIdleList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mIdleList[i].mHandler->mCondition))
+ DetachSocket(mIdleList, &mIdleList[i]);
+ else if (mIdleList[i].mHandler->mPollFlags != 0)
+ MoveToPollList(&mIdleList[i]);
+ }
+
+ SOCKET_LOG((" calling PR_Poll [active=%u idle=%u]\n", mActiveCount, mIdleCount));
+
+#if defined(XP_WIN)
+ // 30 active connections is the historic limit before firefox 7's 256. A few
+ // windows systems have troubles with the higher limit, so actively probe a
+ // limit the first time we exceed 30.
+ if ((mActiveCount > 30) && !mProbedMaxCount)
+ ProbeMaxCount();
+#endif
+
+ // Measures seconds spent while blocked on PR_Poll
+ uint32_t pollInterval = 0;
+ int32_t n = 0;
+ *pollDuration = 0;
+ if (!gIOService->IsNetTearingDown()) {
+ // Let's not do polling during shutdown.
+#if defined(XP_WIN)
+ StartPolling();
+#endif
+ n = Poll(&pollInterval, pollDuration);
+#if defined(XP_WIN)
+ EndPolling();
+#endif
+ }
+
+ if (n < 0) {
+ SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(),
+ PR_GetOSError()));
+ }
+ else {
+ //
+ // service "active" sockets...
+ //
+ uint32_t numberOfOnSocketReadyCalls = 0;
+ for (i=0; i<int32_t(mActiveCount); ++i) {
+ PRPollDesc &desc = mPollList[i+1];
+ SocketContext &s = mActiveList[i];
+ if (n > 0 && desc.out_flags != 0) {
+ s.mElapsedTime = 0;
+ s.mHandler->OnSocketReady(desc.fd, desc.out_flags);
+ numberOfOnSocketReadyCalls++;
+ }
+ // check for timeout errors unless disabled...
+ else if (s.mHandler->mPollTimeout != UINT16_MAX) {
+ // update elapsed time counter
+ // (NOTE: We explicitly cast UINT16_MAX to be an unsigned value
+ // here -- otherwise, some compilers will treat it as signed,
+ // which makes them fire signed/unsigned-comparison build
+ // warnings for the comparison against 'pollInterval'.)
+ if (MOZ_UNLIKELY(pollInterval >
+ static_cast<uint32_t>(UINT16_MAX) -
+ s.mElapsedTime))
+ s.mElapsedTime = UINT16_MAX;
+ else
+ s.mElapsedTime += uint16_t(pollInterval);
+ // check for timeout expiration
+ if (s.mElapsedTime >= s.mHandler->mPollTimeout) {
+ s.mElapsedTime = 0;
+ s.mHandler->OnSocketReady(desc.fd, -1);
+ numberOfOnSocketReadyCalls++;
+ }
+ }
+ }
+ if (mTelemetryEnabledPref) {
+ Telemetry::Accumulate(
+ Telemetry::STS_NUMBER_OF_ONSOCKETREADY_CALLS,
+ numberOfOnSocketReadyCalls);
+ }
+
+ //
+ // check for "dead" sockets and remove them (need to do this in
+ // reverse order obviously).
+ //
+ for (i=mActiveCount-1; i>=0; --i) {
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition))
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+
+ if (n != 0 && (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT))) {
+ MutexAutoLock lock(mLock);
+
+ // acknowledge pollable event (should not block)
+ if (mPollableEvent &&
+ ((mPollList[0].out_flags & PR_POLL_EXCEPT) ||
+ !mPollableEvent->Clear())) {
+ // On Windows, the TCP loopback connection in the
+ // pollable event may become broken when a laptop
+ // switches between wired and wireless networks or
+ // wakes up from hibernation. We try to create a
+ // new pollable event. If that fails, we fall back
+ // on "busy wait".
+ NS_WARNING("Trying to repair mPollableEvent");
+ mPollableEvent.reset(new PollableEvent());
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ }
+ SOCKET_LOG(("running socket transport thread without "
+ "a pollable event now valid=%d", !!mPollableEvent));
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::UpdateSendBufferPref(nsIPrefBranch *pref)
+{
+ int32_t bufferSize;
+
+ // If the pref is set, honor it. 0 means use OS defaults.
+ nsresult rv = pref->GetIntPref(SEND_BUFFER_PREF, &bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ mSendBufferSize = bufferSize;
+ return;
+ }
+
+#if defined(XP_WIN)
+ // If the pref is not set but this is windows set it depending on windows version
+ if (!IsWin2003OrLater()) { // windows xp
+ mSendBufferSize = 131072;
+ } else { // vista or later
+ mSendBufferSize = 131072 * 4;
+ }
+#endif
+}
+
+nsresult
+nsSocketTransportService::UpdatePrefs()
+{
+ mSendBufferSize = 0;
+
+ nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (tmpPrefService) {
+ UpdateSendBufferPref(tmpPrefService);
+
+ // Default TCP Keepalive Values.
+ int32_t keepaliveIdleTimeS;
+ nsresult rv = tmpPrefService->GetIntPref(KEEPALIVE_IDLE_TIME_PREF,
+ &keepaliveIdleTimeS);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS,
+ 1, kMaxTCPKeepIdle);
+
+ int32_t keepaliveRetryIntervalS;
+ rv = tmpPrefService->GetIntPref(KEEPALIVE_RETRY_INTERVAL_PREF,
+ &keepaliveRetryIntervalS);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveRetryIntervalS = clamped(keepaliveRetryIntervalS,
+ 1, kMaxTCPKeepIntvl);
+
+ int32_t keepaliveProbeCount;
+ rv = tmpPrefService->GetIntPref(KEEPALIVE_PROBE_COUNT_PREF,
+ &keepaliveProbeCount);
+ if (NS_SUCCEEDED(rv))
+ mKeepaliveProbeCount = clamped(keepaliveProbeCount,
+ 1, kMaxTCPKeepCount);
+ bool keepaliveEnabled = false;
+ rv = tmpPrefService->GetBoolPref(KEEPALIVE_ENABLED_PREF,
+ &keepaliveEnabled);
+ if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) {
+ mKeepaliveEnabledPref = keepaliveEnabled;
+ OnKeepaliveEnabledPrefChange();
+ }
+
+ int32_t maxTimePref;
+ rv = tmpPrefService->GetIntPref(MAX_TIME_BETWEEN_TWO_POLLS,
+ &maxTimePref);
+ if (NS_SUCCEEDED(rv) && maxTimePref >= 0) {
+ mMaxTimePerPollIter = maxTimePref;
+ }
+
+ bool telemetryPref = false;
+ rv = tmpPrefService->GetBoolPref(TELEMETRY_PREF,
+ &telemetryPref);
+ if (NS_SUCCEEDED(rv)) {
+ mTelemetryEnabledPref = telemetryPref;
+ }
+
+ int32_t maxTimeForPrClosePref;
+ rv = tmpPrefService->GetIntPref(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ &maxTimeForPrClosePref);
+ if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >=0) {
+ mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::OnKeepaliveEnabledPrefChange()
+{
+ // Dispatch to socket thread if we're not executing there.
+ if (PR_GetCurrentThread() != gSocketThread) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod(
+ this, &nsSocketTransportService::OnKeepaliveEnabledPrefChange),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s",
+ mKeepaliveEnabledPref ? "enabled" : "disabled"));
+
+ // Notify each socket that keepalive has been en/disabled globally.
+ for (int32_t i = mActiveCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mActiveList[i]);
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mIdleList[i]);
+ }
+}
+
+void
+nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(SocketContext *sock)
+{
+ MOZ_ASSERT(sock, "SocketContext cannot be null!");
+ MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!");
+
+ if (!sock || !sock->mHandler) {
+ return;
+ }
+
+ sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ UpdatePrefs();
+ return NS_OK;
+ }
+
+ if (!strcmp(topic, "profile-initial-state")) {
+ int32_t blipInterval = Preferences::GetInt(BLIP_INTERVAL_PREF, 0);
+ if (blipInterval <= 0) {
+ return NS_OK;
+ }
+
+ return net::NetworkActivityMonitor::Init(blipInterval);
+ }
+
+ if (!strcmp(topic, "last-pb-context-exited")) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this,
+ &nsSocketTransportService::ClosePrivateConnections);
+ nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = nullptr;
+ mSleepPhase = false;
+ }
+
+#if defined(XP_WIN)
+ if (timer == mPollRepairTimer) {
+ DoPollRepair();
+ }
+#endif
+
+ } else if (!strcmp(topic, NS_WIDGET_SLEEP_OBSERVER_TOPIC)) {
+ mSleepPhase = true;
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ if (mSleepPhase && !mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Init(this, 2000, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+ } else if (!strcmp(topic, "xpcom-shutdown-threads")) {
+ ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+void
+nsSocketTransportService::ClosePrivateConnections()
+{
+ // Must be called on the socket thread.
+#ifdef DEBUG
+ bool onSTSThread;
+ IsOnCurrentThread(&onSTSThread);
+ MOZ_ASSERT(onSTSThread);
+#endif
+
+ for (int32_t i = mActiveCount - 1; i >= 0; --i) {
+ if (mActiveList[i].mHandler->mIsPrivate) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ if (mIdleList[i].mHandler->mIsPrivate) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ }
+ }
+
+ ClearPrivateSSLState();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetSendBufferSize(int32_t *value)
+{
+ *value = mSendBufferSize;
+ return NS_OK;
+}
+
+
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+#include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+#include <sys/resource.h>
+#endif
+
+// Right now the only need to do this is on windows.
+#if defined(XP_WIN)
+void
+nsSocketTransportService::ProbeMaxCount()
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+ if (mProbedMaxCount)
+ return;
+ mProbedMaxCount = true;
+
+ // Allocate and test a PR_Poll up to the gMaxCount number of unconnected
+ // sockets. See bug 692260 - windows should be able to handle 1000 sockets
+ // in select() without a problem, but LSPs have been known to balk at lower
+ // numbers. (64 in the bug).
+
+ // Allocate
+ struct PRPollDesc pfd[SOCKET_LIMIT_TARGET];
+ uint32_t numAllocated = 0;
+
+ for (uint32_t index = 0 ; index < gMaxCount; ++index) {
+ pfd[index].in_flags = PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pfd[index].out_flags = 0;
+ pfd[index].fd = PR_OpenTCPSocket(PR_AF_INET);
+ if (!pfd[index].fd) {
+ SOCKET_LOG(("Socket Limit Test index %d failed\n", index));
+ if (index < SOCKET_LIMIT_MIN)
+ gMaxCount = SOCKET_LIMIT_MIN;
+ else
+ gMaxCount = index;
+ break;
+ }
+ ++numAllocated;
+ }
+
+ // Test
+ static_assert(SOCKET_LIMIT_MIN >= 32U, "Minimum Socket Limit is >= 32");
+ while (gMaxCount <= numAllocated) {
+ int32_t rv = PR_Poll(pfd, gMaxCount, PR_MillisecondsToInterval(0));
+
+ SOCKET_LOG(("Socket Limit Test poll() size=%d rv=%d\n",
+ gMaxCount, rv));
+
+ if (rv >= 0)
+ break;
+
+ SOCKET_LOG(("Socket Limit Test poll confirmationSize=%d rv=%d error=%d\n",
+ gMaxCount, rv, PR_GetError()));
+
+ gMaxCount -= 32;
+ if (gMaxCount <= SOCKET_LIMIT_MIN) {
+ gMaxCount = SOCKET_LIMIT_MIN;
+ break;
+ }
+ }
+
+ // Free
+ for (uint32_t index = 0 ; index < numAllocated; ++index)
+ if (pfd[index].fd)
+ PR_Close(pfd[index].fd);
+
+ Telemetry::Accumulate(Telemetry::NETWORK_PROBE_MAXCOUNT, gMaxCount);
+ SOCKET_LOG(("Socket Limit Test max was confirmed at %d\n", gMaxCount));
+}
+#endif // windows
+
+PRStatus
+nsSocketTransportService::DiscoverMaxCount()
+{
+ gMaxCount = SOCKET_LIMIT_MIN;
+
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+ // On unix and os x network sockets and file
+ // descriptors are the same. OS X comes defaulted at 256,
+ // most linux at 1000. We can reliably use [sg]rlimit to
+ // query that and raise it if needed.
+
+ struct rlimit rlimitData;
+ if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) // rlimit broken - use min
+ return PR_SUCCESS;
+
+ if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target!
+ gMaxCount = SOCKET_LIMIT_TARGET;
+ return PR_SUCCESS;
+ }
+
+ int32_t maxallowed = rlimitData.rlim_max;
+ if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) {
+ return PR_SUCCESS; // so small treat as if rlimit is broken
+ }
+
+ if ((maxallowed == -1) || // no hard cap - ok to set target
+ ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) {
+ maxallowed = SOCKET_LIMIT_TARGET;
+ }
+
+ rlimitData.rlim_cur = maxallowed;
+ setrlimit(RLIMIT_NOFILE, &rlimitData);
+ if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) &&
+ (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) {
+ gMaxCount = rlimitData.rlim_cur;
+ }
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+ // >= XP is confirmed to have at least 1000
+ static_assert(SOCKET_LIMIT_TARGET <= 1000, "SOCKET_LIMIT_TARGET max value is 1000");
+ gMaxCount = SOCKET_LIMIT_TARGET;
+#else
+ // other platforms are harder to test - so leave at safe legacy value
+#endif
+
+ return PR_SUCCESS;
+}
+
+
+// Used to return connection info to Dashboard.cpp
+void
+nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo> *data,
+ struct SocketContext *context, bool aActive)
+{
+ if (context->mHandler->mIsPrivate)
+ return;
+ PRFileDesc *aFD = context->mFD;
+
+ PRFileDesc *idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER);
+
+ NS_ENSURE_TRUE_VOID(idLayer);
+
+ bool tcp = PR_GetDescType(idLayer) == PR_DESC_SOCKET_TCP;
+
+ PRNetAddr peer_addr;
+ PodZero(&peer_addr);
+ PRStatus rv = PR_GetPeerName(aFD, &peer_addr);
+ if (rv != PR_SUCCESS)
+ return;
+
+ char host[64] = {0};
+ rv = PR_NetAddrToString(&peer_addr, host, sizeof(host));
+ if (rv != PR_SUCCESS)
+ return;
+
+ uint16_t port;
+ if (peer_addr.raw.family == PR_AF_INET)
+ port = peer_addr.inet.port;
+ else
+ port = peer_addr.ipv6.port;
+ port = PR_ntohs(port);
+ uint64_t sent = context->mHandler->ByteCountSent();
+ uint64_t received = context->mHandler->ByteCountReceived();
+ SocketInfo info = { nsCString(host), sent, received, port, aActive, tcp };
+
+ data->AppendElement(info);
+}
+
+void
+nsSocketTransportService::GetSocketConnections(nsTArray<SocketInfo> *data)
+{
+ NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+ for (uint32_t i = 0; i < mActiveCount; i++)
+ AnalyzeConnection(data, &mActiveList[i], true);
+ for (uint32_t i = 0; i < mIdleCount; i++)
+ AnalyzeConnection(data, &mIdleList[i], false);
+}
+
+#if defined(XP_WIN)
+void
+nsSocketTransportService::StartPollWatchdog()
+{
+ MutexAutoLock lock(mLock);
+
+ // Poll can hang sometimes. If we are in shutdown, we are going to start a
+ // watchdog. If we do not exit poll within REPAIR_POLLABLE_EVENT_TIME
+ // signal a pollable event again.
+ MOZ_ASSERT(gIOService->IsNetTearingDown());
+ if (mPolling && !mPollRepairTimer) {
+ mPollRepairTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ mPollRepairTimer->Init(this, REPAIR_POLLABLE_EVENT_TIME,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+void
+nsSocketTransportService::DoPollRepair()
+{
+ MutexAutoLock lock(mLock);
+ if (mPolling && mPollableEvent) {
+ mPollableEvent->Signal();
+ } else if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+void
+nsSocketTransportService::StartPolling()
+{
+ MutexAutoLock lock(mLock);
+ mPolling = true;
+}
+
+void
+nsSocketTransportService::EndPolling()
+{
+ MutexAutoLock lock(mLock);
+ mPolling = false;
+ if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h
new file mode 100644
index 000000000..81c806793
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -0,0 +1,282 @@
+/* vim:set ts=4 sw=4 sts=4 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransportService2_h__
+#define nsSocketTransportService2_h__
+
+#include "nsPISocketTransportService.h"
+#include "nsIThreadInternal.h"
+#include "nsIRunnable.h"
+#include "nsEventQueue.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "mozilla/Logging.h"
+#include "prinit.h"
+#include "nsIObserver.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+#include "mozilla/UniquePtr.h"
+#include "PollableEvent.h"
+
+class nsASocketHandler;
+struct PRPollDesc;
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+//
+// set MOZ_LOG=nsSocketTransport:5
+//
+extern LazyLogModule gSocketTransportLog;
+#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args)
+#define SOCKET_LOG_ENABLED() MOZ_LOG_TEST(gSocketTransportLog, LogLevel::Debug)
+
+//
+// set MOZ_LOG=UDPSocket:5
+//
+extern LazyLogModule gUDPSocketLog;
+#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
+#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+
+#define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT
+
+//-----------------------------------------------------------------------------
+
+// These maximums are borrowed from the linux kernel.
+static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours.
+static const int32_t kMaxTCPKeepIntvl = 32767;
+static const int32_t kMaxTCPKeepCount = 127;
+static const int32_t kDefaultTCPKeepCount =
+#if defined (XP_WIN)
+ 10; // Hardcoded in Windows.
+#elif defined (XP_MACOSX)
+ 8; // Hardcoded in OSX.
+#else
+ 4; // Specifiable in Linux.
+#endif
+
+class LinkedRunnableEvent final : public LinkedListElement<LinkedRunnableEvent>
+{
+public:
+ explicit LinkedRunnableEvent(nsIRunnable *event) : mEvent(event) {}
+ ~LinkedRunnableEvent() {}
+
+ already_AddRefed<nsIRunnable> TakeEvent()
+ {
+ return mEvent.forget();
+ }
+private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransportService final : public nsPISocketTransportService
+ , public nsIEventTarget
+ , public nsIThreadObserver
+ , public nsIRunnable
+ , public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPISOCKETTRANSPORTSERVICE
+ NS_DECL_NSISOCKETTRANSPORTSERVICE
+ NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+ using nsIEventTarget::Dispatch;
+
+ nsSocketTransportService();
+
+ // Max Socket count may need to get initialized/used by nsHttpHandler
+ // before this class is initialized.
+ static uint32_t gMaxCount;
+ static PRCallOnceType gMaxCountInitOnce;
+ static PRStatus DiscoverMaxCount();
+
+ bool CanAttachSocket();
+
+ // Called by the networking dashboard on the socket thread only
+ // Fills the passed array with socket information
+ void GetSocketConnections(nsTArray<SocketInfo> *);
+ uint64_t GetSentBytes() { return mSentBytesCount; }
+ uint64_t GetReceivedBytes() { return mReceivedBytesCount; }
+
+ // Returns true if keepalives are enabled in prefs.
+ bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; }
+
+ bool IsTelemetryEnabledAndNotSleepPhase() { return mTelemetryEnabledPref &&
+ !mSleepPhase; }
+ PRIntervalTime MaxTimeForPrClosePref() {return mMaxTimeForPrClosePref; }
+protected:
+
+ virtual ~nsSocketTransportService();
+
+private:
+
+ //-------------------------------------------------------------------------
+ // misc (any thread)
+ //-------------------------------------------------------------------------
+
+ nsCOMPtr<nsIThread> mThread; // protected by mLock
+ UniquePtr<PollableEvent> mPollableEvent;
+
+ // Returns mThread, protecting the get-and-addref with mLock
+ already_AddRefed<nsIThread> GetThreadSafely();
+
+ //-------------------------------------------------------------------------
+ // initialization and shutdown (any thread)
+ //-------------------------------------------------------------------------
+
+ Mutex mLock;
+ bool mInitialized;
+ bool mShuttingDown;
+ // indicates whether we are currently in the
+ // process of shutting down
+ bool mOffline;
+ bool mGoingOffline;
+
+ // Detaches all sockets.
+ void Reset(bool aGuardLocals);
+
+ nsresult ShutdownThread();
+
+ //-------------------------------------------------------------------------
+ // socket lists (socket thread only)
+ //
+ // only "active" sockets are on the poll list. the active list is kept
+ // in sync with the poll list such that:
+ //
+ // mActiveList[k].mFD == mPollList[k+1].fd
+ //
+ // where k=0,1,2,...
+ //-------------------------------------------------------------------------
+
+ struct SocketContext
+ {
+ PRFileDesc *mFD;
+ nsASocketHandler *mHandler;
+ uint16_t mElapsedTime; // time elapsed w/o activity
+ };
+
+ SocketContext *mActiveList; /* mListSize entries */
+ SocketContext *mIdleList; /* mListSize entries */
+ nsIThread *mRawThread;
+
+ uint32_t mActiveListSize;
+ uint32_t mIdleListSize;
+ uint32_t mActiveCount;
+ uint32_t mIdleCount;
+
+ nsresult DetachSocket(SocketContext *, SocketContext *);
+ nsresult AddToIdleList(SocketContext *);
+ nsresult AddToPollList(SocketContext *);
+ void RemoveFromIdleList(SocketContext *);
+ void RemoveFromPollList(SocketContext *);
+ void MoveToIdleList(SocketContext *sock);
+ void MoveToPollList(SocketContext *sock);
+
+ bool GrowActiveList();
+ bool GrowIdleList();
+ void InitMaxCount();
+
+ // Total bytes number transfered through all the sockets except active ones
+ uint64_t mSentBytesCount;
+ uint64_t mReceivedBytesCount;
+ //-------------------------------------------------------------------------
+ // poll list (socket thread only)
+ //
+ // first element of the poll list is mPollableEvent (or null if the pollable
+ // event cannot be created).
+ //-------------------------------------------------------------------------
+
+ PRPollDesc *mPollList; /* mListSize + 1 entries */
+
+ PRIntervalTime PollTimeout(); // computes ideal poll timeout
+ nsresult DoPollIteration(TimeDuration *pollDuration);
+ // perfoms a single poll iteration
+ int32_t Poll(uint32_t *interval,
+ TimeDuration *pollDuration);
+ // calls PR_Poll. the out param
+ // interval indicates the poll
+ // duration in seconds.
+ // pollDuration is used only for
+ // telemetry
+
+ //-------------------------------------------------------------------------
+ // pending socket queue - see NotifyWhenCanAttachSocket
+ //-------------------------------------------------------------------------
+ AutoCleanLinkedList<LinkedRunnableEvent> mPendingSocketQueue;
+
+ // Preference Monitor for SendBufferSize and Keepalive prefs.
+ nsresult UpdatePrefs();
+ void UpdateSendBufferPref(nsIPrefBranch *);
+ int32_t mSendBufferSize;
+ // Number of seconds of connection is idle before first keepalive ping.
+ int32_t mKeepaliveIdleTimeS;
+ // Number of seconds between retries should keepalive pings fail.
+ int32_t mKeepaliveRetryIntervalS;
+ // Number of keepalive probes to send.
+ int32_t mKeepaliveProbeCount;
+ // True if TCP keepalive is enabled globally.
+ bool mKeepaliveEnabledPref;
+
+ Atomic<bool> mServingPendingQueue;
+ Atomic<int32_t, Relaxed> mMaxTimePerPollIter;
+ Atomic<bool, Relaxed> mTelemetryEnabledPref;
+ Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref;
+
+ // Between a computer going to sleep and waking up the PR_*** telemetry
+ // will be corrupted - so do not record it.
+ Atomic<bool, Relaxed> mSleepPhase;
+ nsCOMPtr<nsITimer> mAfterWakeUpTimer;
+
+ void OnKeepaliveEnabledPrefChange();
+ void NotifyKeepaliveEnabledPrefChange(SocketContext *sock);
+
+ // Socket thread only for dynamically adjusting max socket size
+#if defined(XP_WIN)
+ void ProbeMaxCount();
+#endif
+ bool mProbedMaxCount;
+
+ void AnalyzeConnection(nsTArray<SocketInfo> *data,
+ SocketContext *context, bool aActive);
+
+ void ClosePrivateConnections();
+ void DetachSocketWithGuard(bool aGuardLocals,
+ SocketContext *socketList,
+ int32_t index);
+
+ void MarkTheLastElementOfPendingQueue();
+
+#if defined(XP_WIN)
+ Atomic<bool> mPolling;
+ nsCOMPtr<nsITimer> mPollRepairTimer;
+ void StartPollWatchdog();
+ void DoPollRepair();
+ void StartPolling();
+ void EndPolling();
+#endif
+};
+
+extern nsSocketTransportService *gSocketTransportService;
+extern Atomic<PRThread*, Relaxed> gSocketThread;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransportService_h__
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
new file mode 100644
index 000000000..922d8a3ba
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,3619 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IPCMessageUtils.h"
+
+#include "nsStandardURL.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIIDNService.h"
+#include "mozilla/Logging.h"
+#include "nsAutoPtr.h"
+#include "nsIURLParser.h"
+#include "nsNetCID.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+#include <algorithm>
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsContentUtils.h"
+#include "prprf.h"
+#include "nsReadableUtils.h"
+
+using mozilla::dom::EncodingUtils;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
+static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
+
+nsIIDNService *nsStandardURL::gIDN = nullptr;
+bool nsStandardURL::gInitialized = false;
+bool nsStandardURL::gEscapeUTF8 = true;
+bool nsStandardURL::gAlwaysEncodeInUTF8 = true;
+char nsStandardURL::gHostLimitDigits[] = { '/', '\\', '?', '#', 0 };
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static LazyLogModule gStandardURLLog("nsStandardURL");
+
+// The Chromium code defines its own LOG macro which we don't want
+#undef LOG
+#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
+
+//----------------------------------------------------------------------------
+
+#define ENSURE_MUTABLE() \
+ PR_BEGIN_MACRO \
+ if (!mMutable) { \
+ NS_WARNING("attempt to modify an immutable nsStandardURL"); \
+ return NS_ERROR_ABORT; \
+ } \
+ PR_END_MACRO
+
+//----------------------------------------------------------------------------
+
+#ifdef MOZ_RUST_URLPARSE
+extern "C" int32_t c_fn_set_size(void * container, size_t size)
+{
+ ((nsACString *) container)->SetLength(size);
+ return 0;
+}
+
+extern "C" char * c_fn_get_buffer(void * container)
+{
+ return ((nsACString *) container)->BeginWriting();
+}
+#endif
+
+static nsresult
+EncodeString(nsIUnicodeEncoder *encoder, const nsAFlatString &str, nsACString &result)
+{
+ nsresult rv;
+ int32_t len = str.Length();
+ int32_t maxlen;
+
+ rv = encoder->GetMaxLength(str.get(), len, &maxlen);
+ if (NS_FAILED(rv))
+ return rv;
+
+ char buf[256], *p = buf;
+ if (uint32_t(maxlen) > sizeof(buf) - 1) {
+ p = (char *) malloc(maxlen + 1);
+ if (!p)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = encoder->Convert(str.get(), &len, p, &maxlen);
+ if (NS_FAILED(rv))
+ goto end;
+ if (rv == NS_ERROR_UENC_NOMAPPING) {
+ NS_WARNING("unicode conversion failed");
+ rv = NS_ERROR_UNEXPECTED;
+ goto end;
+ }
+ p[maxlen] = 0;
+ result.Assign(p);
+
+ len = sizeof(buf) - 1;
+ rv = encoder->Finish(buf, &len);
+ if (NS_FAILED(rv))
+ goto end;
+ buf[len] = 0;
+ result.Append(buf);
+
+end:
+ encoder->Reset();
+
+ if (p != buf)
+ free(p);
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsPrefObserver
+//----------------------------------------------------------------------------
+
+#define NS_NET_PREF_ESCAPEUTF8 "network.standard-url.escape-utf8"
+#define NS_NET_PREF_ALWAYSENCODEINUTF8 "network.standard-url.encode-utf8"
+
+NS_IMPL_ISUPPORTS(nsStandardURL::nsPrefObserver, nsIObserver)
+
+NS_IMETHODIMP nsStandardURL::
+nsPrefObserver::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(subject) );
+ if (prefBranch) {
+ PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
+ }
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsSegmentEncoder
+//----------------------------------------------------------------------------
+
+nsStandardURL::
+nsSegmentEncoder::nsSegmentEncoder(const char *charset)
+ : mCharset(charset)
+{
+}
+
+int32_t nsStandardURL::
+nsSegmentEncoder::EncodeSegmentCount(const char *str,
+ const URLSegment &seg,
+ int16_t mask,
+ nsAFlatCString &result,
+ bool &appended,
+ uint32_t extraLen)
+{
+ // extraLen is characters outside the segment that will be
+ // added when the segment is not empty (like the @ following
+ // a username).
+ appended = false;
+ if (!str)
+ return 0;
+ int32_t len = 0;
+ if (seg.mLen > 0) {
+ uint32_t pos = seg.mPos;
+ len = seg.mLen;
+
+ // first honor the origin charset if appropriate. as an optimization,
+ // only do this if the segment is non-ASCII. Further, if mCharset is
+ // null or the empty string then the origin charset is UTF-8 and there
+ // is nothing to do.
+ nsAutoCString encBuf;
+ if (mCharset && *mCharset && !nsCRT::IsAscii(str + pos, len)) {
+ // we have to encode this segment
+ if (mEncoder || InitUnicodeEncoder()) {
+ NS_ConvertUTF8toUTF16 ucsBuf(Substring(str + pos, str + pos + len));
+ if (NS_SUCCEEDED(EncodeString(mEncoder, ucsBuf, encBuf))) {
+ str = encBuf.get();
+ pos = 0;
+ len = encBuf.Length();
+ }
+ // else some failure occurred... assume UTF-8 is ok.
+ }
+ }
+
+ // escape per RFC2396 unless UTF-8 and allowed by preferences
+ int16_t escapeFlags = (gEscapeUTF8 || mEncoder) ? 0 : esc_OnlyASCII;
+
+ uint32_t initLen = result.Length();
+
+ // now perform any required escaping
+ if (NS_EscapeURL(str + pos, len, mask | escapeFlags, result)) {
+ len = result.Length() - initLen;
+ appended = true;
+ }
+ else if (str == encBuf.get()) {
+ result += encBuf; // append only!!
+ len = encBuf.Length();
+ appended = true;
+ }
+ len += extraLen;
+ }
+ return len;
+}
+
+const nsACString &nsStandardURL::
+nsSegmentEncoder::EncodeSegment(const nsASingleFragmentCString &str,
+ int16_t mask,
+ nsAFlatCString &result)
+{
+ const char *text;
+ bool encoded;
+ EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask, result, encoded);
+ if (encoded)
+ return result;
+ return str;
+}
+
+bool nsStandardURL::
+nsSegmentEncoder::InitUnicodeEncoder()
+{
+ NS_ASSERTION(!mEncoder, "Don't call this if we have an encoder already!");
+ // "replacement" won't survive another label resolution
+ nsDependentCString label(mCharset);
+ if (label.EqualsLiteral("replacement")) {
+ mEncoder = EncodingUtils::EncoderForEncoding(label);
+ return true;
+ }
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(label, encoding)) {
+ return false;
+ }
+ mEncoder = EncodingUtils::EncoderForEncoding(encoding);
+ return true;
+}
+
+#define GET_SEGMENT_ENCODER_INTERNAL(name, useUTF8) \
+ nsSegmentEncoder name(useUTF8 ? nullptr : mOriginCharset.get())
+
+#define GET_SEGMENT_ENCODER(name) \
+ GET_SEGMENT_ENCODER_INTERNAL(name, gAlwaysEncodeInUTF8)
+
+#define GET_QUERY_ENCODER(name) \
+ GET_SEGMENT_ENCODER_INTERNAL(name, false)
+
+//----------------------------------------------------------------------------
+// nsStandardURL <public>
+//----------------------------------------------------------------------------
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+static PRCList gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mDefaultPort(-1)
+ , mPort(-1)
+ , mHostA(nullptr)
+ , mHostEncoding(eEncoding_ASCII)
+ , mSpecEncoding(eEncoding_Unknown)
+ , mURLType(URLTYPE_STANDARD)
+ , mMutable(true)
+ , mSupportsFileURL(aSupportsFileURL)
+{
+ LOG(("Creating nsStandardURL @%p\n", this));
+
+ if (!gInitialized) {
+ gInitialized = true;
+ InitGlobalObjects();
+ }
+
+ // default parser in case nsIStandardURL::Init is never called
+ mParser = net_GetStdURLParser();
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ memset(&mDebugCList, 0, sizeof(mDebugCList));
+ if (NS_IsMainThread()) {
+ if (aTrackURL) {
+ PR_APPEND_LINK(&mDebugCList, &gAllURLs);
+ } else {
+ PR_INIT_CLIST(&mDebugCList);
+ }
+ }
+#endif
+}
+
+nsStandardURL::~nsStandardURL()
+{
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+ if (mHostA) {
+ free(mHostA);
+ }
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (NS_IsMainThread()) {
+ if (!PR_CLIST_IS_EMPTY(&mDebugCList)) {
+ PR_REMOVE_LINK(&mDebugCList);
+ }
+ }
+#endif
+}
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+struct DumpLeakedURLs {
+ DumpLeakedURLs() {}
+ ~DumpLeakedURLs();
+};
+
+DumpLeakedURLs::~DumpLeakedURLs()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!PR_CLIST_IS_EMPTY(&gAllURLs)) {
+ printf("Leaked URLs:\n");
+ for (PRCList *l = PR_LIST_HEAD(&gAllURLs); l != &gAllURLs; l = PR_NEXT_LINK(l)) {
+ nsStandardURL *url = reinterpret_cast<nsStandardURL*>(reinterpret_cast<char*>(l) - offsetof(nsStandardURL, mDebugCList));
+ url->PrintSpec();
+ }
+ }
+}
+#endif
+
+void
+nsStandardURL::InitGlobalObjects()
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID) );
+ if (prefBranch) {
+ nsCOMPtr<nsIObserver> obs( new nsPrefObserver() );
+ prefBranch->AddObserver(NS_NET_PREF_ESCAPEUTF8, obs.get(), false);
+ prefBranch->AddObserver(NS_NET_PREF_ALWAYSENCODEINUTF8, obs.get(), false);
+
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ PR_INIT_CLIST(&gAllURLs);
+#endif
+}
+
+void
+nsStandardURL::ShutdownGlobalObjects()
+{
+ NS_IF_RELEASE(gIDN);
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (gInitialized) {
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ static DumpLeakedURLs d;
+ }
+#endif
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <private>
+//----------------------------------------------------------------------------
+
+void
+nsStandardURL::Clear()
+{
+ mSpec.Truncate();
+
+ mPort = -1;
+
+ mScheme.Reset();
+ mAuthority.Reset();
+ mUsername.Reset();
+ mPassword.Reset();
+ mHost.Reset();
+ mHostEncoding = eEncoding_ASCII;
+
+ mPath.Reset();
+ mFilepath.Reset();
+ mDirectory.Reset();
+ mBasename.Reset();
+
+ mExtension.Reset();
+ mQuery.Reset();
+ mRef.Reset();
+
+ InvalidateCache();
+}
+
+void
+nsStandardURL::InvalidateCache(bool invalidateCachedFile)
+{
+ if (invalidateCachedFile)
+ mFile = nullptr;
+ if (mHostA) {
+ free(mHostA);
+ mHostA = nullptr;
+ }
+ mSpecEncoding = eEncoding_Unknown;
+}
+
+// |base| should be 8, 10 or 16. Not check the precondition for performance.
+/* static */ inline bool
+nsStandardURL::IsValidOfBase(unsigned char c, const uint32_t base) {
+ MOZ_ASSERT(base == 8 || base == 10 || base == 16, "invalid base");
+ if ('0' <= c && c <= '7') {
+ return true;
+ } else if (c == '8' || c== '9') {
+ return base != 8;
+ } else if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) {
+ return base == 16;
+ }
+ return false;
+}
+
+/* static */ inline nsresult
+nsStandardURL::ParseIPv4Number(nsCString &input, uint32_t &number)
+{
+ if (input.Length() == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t base;
+ uint32_t prefixLength = 0;
+
+ if (input[0] == '0') {
+ if (input.Length() == 1) {
+ base = 10;
+ } else if (input[1] == 'x' || input[1] == 'X') {
+ base = 16;
+ prefixLength = 2;
+ } else {
+ base = 8;
+ prefixLength = 1;
+ }
+ } else {
+ base = 10;
+ }
+ if (prefixLength == input.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ // ignore leading zeros to calculate the valid length of number
+ while (prefixLength < input.Length() && input[prefixLength] == '0') {
+ prefixLength++;
+ }
+ // all zero case
+ if (prefixLength == input.Length()) {
+ number = 0;
+ return NS_OK;
+ }
+ // overflow case
+ if (input.Length() - prefixLength > 16) {
+ return NS_ERROR_FAILURE;
+ }
+ for (uint32_t i = prefixLength; i < input.Length(); ++i) {
+ if (!IsValidOfBase(input[i], base)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ const char* fmt = "";
+ switch (base) {
+ case 8:
+ fmt = "%llo";
+ break;
+ case 10:
+ fmt = "%lli";
+ break;
+ case 16:
+ fmt = "%llx";
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ uint64_t number64;
+ if (PR_sscanf(input.get(), fmt, &number64) == 1 &&
+ number64 <= 0xffffffffu) {
+ number = number64;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
+/* static */ nsresult
+nsStandardURL::NormalizeIPv4(const nsCSubstring &host, nsCString &result)
+{
+ if (host.Length() == 0 ||
+ host[0] < '0' || '9' < host[0] || // bail-out fast
+ FindInReadable(NS_LITERAL_CSTRING(".."), host)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<nsCString> parts;
+ if (!ParseString(host, '.', parts) ||
+ parts.Length() == 0 ||
+ parts.Length() > 4) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t n = 0;
+ nsTArray<int32_t> numbers;
+ for (uint32_t i = 0; i < parts.Length(); ++i) {
+ if (NS_FAILED(ParseIPv4Number(parts[i], n))) {
+ return NS_ERROR_FAILURE;
+ }
+ numbers.AppendElement(n);
+ }
+ uint32_t ipv4 = numbers.LastElement();
+ static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu,
+ 0xffffu, 0xffu};
+ if (ipv4 > upperBounds[numbers.Length() - 1]) {
+ return NS_ERROR_FAILURE;
+ }
+ for (uint32_t i = 0; i < numbers.Length() - 1; ++i) {
+ if (numbers[i] > 255) {
+ return NS_ERROR_FAILURE;
+ }
+ ipv4 += numbers[i] << (8 * (3 - i));
+ }
+
+ uint8_t ipSegments[4];
+ NetworkEndian::writeUint32(ipSegments, ipv4);
+ result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
+ ipSegments[2], ipSegments[3]);
+
+ return NS_OK;
+}
+
+nsresult
+nsStandardURL::NormalizeIDN(const nsCSubstring &host, nsCString &result)
+{
+ // If host is ACE, then convert to UTF-8. Else, if host is already UTF-8,
+ // then make sure it is normalized per IDN.
+
+ // this function returns true if normalization succeeds.
+
+ // NOTE: As a side-effect this function sets mHostEncoding. While it would
+ // be nice to avoid side-effects in this function, the implementation of
+ // this function is already somewhat bound to the behavior of the
+ // callsites. Anyways, this function exists to avoid code duplication, so
+ // side-effects abound :-/
+
+ NS_ASSERTION(mHostEncoding == eEncoding_ASCII, "unexpected default encoding");
+
+ bool isASCII;
+ if (!gIDN) {
+ nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
+ if (serv) {
+ NS_ADDREF(gIDN = serv.get());
+ }
+ }
+
+ result.Truncate();
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (gIDN) {
+ rv = gIDN->ConvertToDisplayIDN(host, &isASCII, result);
+ if (NS_SUCCEEDED(rv) && !isASCII) {
+ mHostEncoding = eEncoding_UTF8;
+ }
+ }
+
+ return rv;
+}
+
+bool
+nsStandardURL::ValidIPv6orHostname(const char *host, uint32_t length)
+{
+ if (!host || !*host) {
+ // Should not be NULL or empty string
+ return false;
+ }
+
+ if (length != strlen(host)) {
+ // Embedded null
+ return false;
+ }
+
+ bool openBracket = host[0] == '[';
+ bool closeBracket = host[length - 1] == ']';
+
+ if (openBracket && closeBracket) {
+ return net_IsValidIPv6Addr(host + 1, length - 2);
+ }
+
+ if (openBracket || closeBracket) {
+ // Fail if only one of the brackets is present
+ return false;
+ }
+
+ const char *end = host + length;
+ if (end != net_FindCharInSet(host, end, CONTROL_CHARACTERS " #/:?@[\\]*<>|\"")) {
+ // We still allow % because it is in the ID of addons.
+ // Any percent encoded ASCII characters that are not allowed in the
+ // hostname are not percent decoded, and will be parsed just fine.
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char *path)
+{
+ net_CoalesceDirs(coalesceFlag, path);
+ int32_t newLen = strlen(path);
+ if (newLen < mPath.mLen) {
+ int32_t diff = newLen - mPath.mLen;
+ mPath.mLen = newLen;
+ mDirectory.mLen += diff;
+ mFilepath.mLen += diff;
+ ShiftFromBasename(diff);
+ }
+}
+
+uint32_t
+nsStandardURL::AppendSegmentToBuf(char *buf, uint32_t i, const char *str,
+ const URLSegment &segInput, URLSegment &segOutput,
+ const nsCString *escapedStr,
+ bool useEscaped, int32_t *diff)
+{
+ MOZ_ASSERT(segInput.mLen == segOutput.mLen);
+
+ if (diff) *diff = 0;
+
+ if (segInput.mLen > 0) {
+ if (useEscaped) {
+ MOZ_ASSERT(diff);
+ segOutput.mLen = escapedStr->Length();
+ *diff = segOutput.mLen - segInput.mLen;
+ memcpy(buf + i, escapedStr->get(), segOutput.mLen);
+ } else {
+ memcpy(buf + i, str + segInput.mPos, segInput.mLen);
+ }
+ segOutput.mPos = i;
+ i += segOutput.mLen;
+ } else {
+ segOutput.mPos = i;
+ }
+ return i;
+}
+
+uint32_t
+nsStandardURL::AppendToBuf(char *buf, uint32_t i, const char *str, uint32_t len)
+{
+ memcpy(buf + i, str, len);
+ return i + len;
+}
+
+// basic algorithm:
+// 1- escape url segments (for improved GetSpec efficiency)
+// 2- allocate spec buffer
+// 3- write url segments
+// 4- update url segment positions and lengths
+nsresult
+nsStandardURL::BuildNormalizedSpec(const char *spec)
+{
+ // Assumptions: all member URLSegments must be relative the |spec| argument
+ // passed to this function.
+
+ // buffers for holding escaped url segments (these will remain empty unless
+ // escaping is required).
+ nsAutoCString encUsername, encPassword, encHost, encDirectory,
+ encBasename, encExtension, encQuery, encRef;
+ bool useEncUsername, useEncPassword, useEncHost = false,
+ useEncDirectory, useEncBasename, useEncExtension, useEncQuery, useEncRef;
+ nsAutoCString portbuf;
+
+ //
+ // escape each URL segment, if necessary, and calculate approximate normalized
+ // spec length.
+ //
+ // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
+
+ uint32_t approxLen = 0;
+
+ // the scheme is already ASCII
+ if (mScheme.mLen > 0)
+ approxLen += mScheme.mLen + 3; // includes room for "://", which we insert always
+
+ // encode URL segments; convert UTF-8 to origin charset and possibly escape.
+ // results written to encXXX variables only if |spec| is not already in the
+ // appropriate encoding.
+ {
+ GET_SEGMENT_ENCODER(encoder);
+ GET_QUERY_ENCODER(queryEncoder);
+ // Items using an extraLen of 1 don't add anything unless mLen > 0
+ // Username@
+ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username, encUsername, useEncUsername, 1);
+ // :password - we insert the ':' even if there's no actual password if "user:@" was in the spec
+ if (mPassword.mLen >= 0)
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password, encPassword, useEncPassword);
+ // mHost is handled differently below due to encoding differences
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort)
+ {
+ // :port
+ portbuf.AppendInt(mPort);
+ approxLen += portbuf.Length() + 1;
+ }
+
+ approxLen += 1; // reserve space for possible leading '/' - may not be needed
+ // Should just use mPath? These are pessimistic, and thus waste space
+ approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory, encDirectory, useEncDirectory, 1);
+ approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName, encBasename, useEncBasename);
+ approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension, encExtension, useEncExtension, 1);
+
+ // These next ones *always* add their leading character even if length is 0
+ // Handles items like "http://#"
+ // ?query
+ if (mQuery.mLen >= 0)
+ approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query, encQuery, useEncQuery);
+ // #ref
+
+ if (mRef.mLen >= 0) {
+ if (nsContentUtils::EncodeDecodeURLHash()) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref,
+ encRef, useEncRef);
+ } else {
+ approxLen += 1 + mRef.mLen;
+ useEncRef = false;
+ }
+ }
+ }
+
+ // do not escape the hostname, if IPv6 address literal, mHost will
+ // already point to a [ ] delimited IPv6 address literal.
+ // However, perform Unicode normalization on it, as IDN does.
+ mHostEncoding = eEncoding_ASCII;
+ // Note that we don't disallow URLs without a host - file:, etc
+ if (mHost.mLen > 0) {
+ nsAutoCString tempHost;
+ NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host, tempHost);
+ if (tempHost.Contains('\0'))
+ return NS_ERROR_MALFORMED_URI; // null embedded in hostname
+ if (tempHost.Contains(' '))
+ return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname
+ nsresult rv = NormalizeIDN(tempHost, encHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!SegmentIs(spec, mScheme, "resource") &&
+ !SegmentIs(spec, mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
+ encHost = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies, if the call was successful.
+ useEncHost = true;
+ approxLen += encHost.Length();
+
+ if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ // We must take a copy of every single segment because they are pointing to
+ // the |spec| while we are changing their value, in case we must use
+ // encoded strings.
+ URLSegment username(mUsername);
+ URLSegment password(mPassword);
+ URLSegment host(mHost);
+ URLSegment path(mPath);
+ URLSegment filepath(mFilepath);
+ URLSegment directory(mDirectory);
+ URLSegment basename(mBasename);
+ URLSegment extension(mExtension);
+ URLSegment query(mQuery);
+ URLSegment ref(mRef);
+
+ //
+ // generate the normalized URL string
+ //
+ // approxLen should be correct or 1 high
+ if (!mSpec.SetLength(approxLen+1, fallible)) // buf needs a trailing '\0' below
+ return NS_ERROR_OUT_OF_MEMORY;
+ char *buf;
+ mSpec.BeginWriting(buf);
+ uint32_t i = 0;
+ int32_t diff = 0;
+
+ if (mScheme.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
+ net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
+ i = AppendToBuf(buf, i, "://", 3);
+ }
+
+ // record authority starting position
+ mAuthority.mPos = i;
+
+ // append authority
+ if (mUsername.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, username, mUsername,
+ &encUsername, useEncUsername, &diff);
+ ShiftFromPassword(diff);
+ if (password.mLen >= 0) {
+ buf[i++] = ':';
+ i = AppendSegmentToBuf(buf, i, spec, password, mPassword,
+ &encPassword, useEncPassword, &diff);
+ ShiftFromHost(diff);
+ }
+ buf[i++] = '@';
+ }
+ if (host.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
+ &diff);
+ ShiftFromPath(diff);
+
+ net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ buf[i++] = ':';
+ // Already formatted while building approxLen
+ i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
+ }
+ }
+
+ // record authority length
+ mAuthority.mLen = i - mAuthority.mPos;
+
+ // path must always start with a "/"
+ if (mPath.mLen <= 0) {
+ LOG(("setting path=/"));
+ mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
+ mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
+ // basename must exist, even if empty (bug 113508)
+ mBasename.mPos = i+1;
+ mBasename.mLen = 0;
+ buf[i++] = '/';
+ }
+ else {
+ uint32_t leadingSlash = 0;
+ if (spec[path.mPos] != '/') {
+ LOG(("adding leading slash to path\n"));
+ leadingSlash = 1;
+ buf[i++] = '/';
+ // basename must exist, even if empty (bugs 113508, 429347)
+ if (mBasename.mLen == -1) {
+ mBasename.mPos = basename.mPos = i;
+ mBasename.mLen = basename.mLen = 0;
+ }
+ }
+
+ // record corrected (file)path starting position
+ mPath.mPos = mFilepath.mPos = i - leadingSlash;
+
+ i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory,
+ &encDirectory, useEncDirectory, &diff);
+ ShiftFromBasename(diff);
+
+ // the directory must end with a '/'
+ if (buf[i-1] != '/') {
+ buf[i++] = '/';
+ mDirectory.mLen++;
+ }
+
+ i = AppendSegmentToBuf(buf, i, spec, basename, mBasename,
+ &encBasename, useEncBasename, &diff);
+ ShiftFromExtension(diff);
+
+ // make corrections to directory segment if leadingSlash
+ if (leadingSlash) {
+ mDirectory.mPos = mPath.mPos;
+ if (mDirectory.mLen >= 0)
+ mDirectory.mLen += leadingSlash;
+ else
+ mDirectory.mLen = 1;
+ }
+
+ if (mExtension.mLen >= 0) {
+ buf[i++] = '.';
+ i = AppendSegmentToBuf(buf, i, spec, extension, mExtension,
+ &encExtension, useEncExtension, &diff);
+ ShiftFromQuery(diff);
+ }
+ // calculate corrected filepath length
+ mFilepath.mLen = i - mFilepath.mPos;
+
+ if (mQuery.mLen >= 0) {
+ buf[i++] = '?';
+ i = AppendSegmentToBuf(buf, i, spec, query, mQuery,
+ &encQuery, useEncQuery,
+ &diff);
+ ShiftFromRef(diff);
+ }
+ if (mRef.mLen >= 0) {
+ buf[i++] = '#';
+ i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
+ &diff);
+ }
+ // calculate corrected path length
+ mPath.mLen = i - mPath.mPos;
+ }
+
+ buf[i] = '\0';
+
+ if (mDirectory.mLen > 1) {
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+ if (SegmentIs(buf,mScheme,"ftp")) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ CoalescePath(coalesceFlag, buf + mDirectory.mPos);
+ }
+ mSpec.SetLength(strlen(buf));
+ NS_ASSERTION(mSpec.Length() <= approxLen, "We've overflowed the mSpec buffer!");
+ return NS_OK;
+}
+
+bool
+nsStandardURL::SegmentIs(const URLSegment &seg, const char *val, bool ignoreCase)
+{
+ // one or both may be null
+ if (!val || mSpec.IsEmpty())
+ return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
+ if (seg.mLen < 0)
+ return false;
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase)
+ return !PL_strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+ else
+ return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+}
+
+bool
+nsStandardURL::SegmentIs(const char* spec, const URLSegment &seg, const char *val, bool ignoreCase)
+{
+ // one or both may be null
+ if (!val || !spec)
+ return (!val && (!spec || seg.mLen < 0));
+ if (seg.mLen < 0)
+ return false;
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase)
+ return !PL_strncasecmp(spec + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+ else
+ return !strncmp(spec + seg.mPos, val, seg.mLen)
+ && (val[seg.mLen] == '\0');
+}
+
+bool
+nsStandardURL::SegmentIs(const URLSegment &seg1, const char *val, const URLSegment &seg2, bool ignoreCase)
+{
+ if (seg1.mLen != seg2.mLen)
+ return false;
+ if (seg1.mLen == -1 || (!val && mSpec.IsEmpty()))
+ return true; // both are empty
+ if (!val)
+ return false;
+ if (ignoreCase)
+ return !PL_strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+ else
+ return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+}
+
+int32_t
+nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const char *val, uint32_t valLen)
+{
+ if (val && valLen) {
+ if (len == 0)
+ mSpec.Insert(val, pos, valLen);
+ else
+ mSpec.Replace(pos, len, nsDependentCString(val, valLen));
+ return valLen - len;
+ }
+
+ // else remove the specified segment
+ mSpec.Cut(pos, len);
+ return -int32_t(len);
+}
+
+int32_t
+nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const nsACString &val)
+{
+ if (len == 0)
+ mSpec.Insert(val, pos);
+ else
+ mSpec.Replace(pos, len, val);
+ return val.Length() - len;
+}
+
+nsresult
+nsStandardURL::ParseURL(const char *spec, int32_t specLen)
+{
+ nsresult rv;
+
+ if (specLen > net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // parse given URL string
+ //
+ rv = mParser->ParseURL(spec, specLen,
+ &mScheme.mPos, &mScheme.mLen,
+ &mAuthority.mPos, &mAuthority.mLen,
+ &mPath.mPos, &mPath.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &mUsername.mPos, &mUsername.mLen,
+ &mPassword.mPos, &mPassword.mLen,
+ &mHost.mPos, &mHost.mLen,
+ &mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Don't allow mPort to be set to this URI's default port
+ if (mPort == mDefaultPort)
+ mPort = -1;
+
+ mUsername.mPos += mAuthority.mPos;
+ mPassword.mPos += mAuthority.mPos;
+ mHost.mPos += mAuthority.mPos;
+ }
+
+ if (mPath.mLen > 0)
+ rv = ParsePath(spec, mPath.mPos, mPath.mLen);
+
+ return rv;
+}
+
+nsresult
+nsStandardURL::ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen)
+{
+ LOG(("ParsePath: %s pathpos %d len %d\n",spec,pathPos,pathLen));
+
+ if (pathLen > net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsresult rv = mParser->ParsePath(spec + pathPos, pathLen,
+ &mFilepath.mPos, &mFilepath.mLen,
+ &mQuery.mPos, &mQuery.mLen,
+ &mRef.mPos, &mRef.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &mDirectory.mPos, &mDirectory.mLen,
+ &mBasename.mPos, &mBasename.mLen,
+ &mExtension.mPos, &mExtension.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mDirectory.mPos += mFilepath.mPos;
+ mBasename.mPos += mFilepath.mPos;
+ mExtension.mPos += mFilepath.mPos;
+ }
+ return NS_OK;
+}
+
+char *
+nsStandardURL::AppendToSubstring(uint32_t pos,
+ int32_t len,
+ const char *tail)
+{
+ // Verify pos and length are within boundaries
+ if (pos > mSpec.Length())
+ return nullptr;
+ if (len < 0)
+ return nullptr;
+ if ((uint32_t)len > (mSpec.Length() - pos))
+ return nullptr;
+ if (!tail)
+ return nullptr;
+
+ uint32_t tailLen = strlen(tail);
+
+ // Check for int overflow for proposed length of combined string
+ if (UINT32_MAX - ((uint32_t)len + 1) < tailLen)
+ return nullptr;
+
+ char *result = (char *) moz_xmalloc(len + tailLen + 1);
+ if (result) {
+ memcpy(result, mSpec.get() + pos, len);
+ memcpy(result + len, tail, tailLen);
+ result[len + tailLen] = '\0';
+ }
+ return result;
+}
+
+nsresult
+nsStandardURL::ReadSegment(nsIBinaryInputStream *stream, URLSegment &seg)
+{
+ nsresult rv;
+
+ rv = stream->Read32(&seg.mPos);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Read32((uint32_t *) &seg.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+nsStandardURL::WriteSegment(nsIBinaryOutputStream *stream, const URLSegment &seg)
+{
+ nsresult rv;
+
+ rv = stream->Write32(seg.mPos);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(seg.mLen));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+/* static */ void
+nsStandardURL::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
+{
+ bool val;
+
+ LOG(("nsStandardURL::PrefsChanged [pref=%s]\n", pref));
+
+#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p))
+#define GOT_PREF(p, b) (NS_SUCCEEDED(prefs->GetBoolPref(p, &b)))
+
+ if (PREF_CHANGED(NS_NET_PREF_ESCAPEUTF8)) {
+ if (GOT_PREF(NS_NET_PREF_ESCAPEUTF8, val))
+ gEscapeUTF8 = val;
+ LOG(("escape UTF-8 %s\n", gEscapeUTF8 ? "enabled" : "disabled"));
+ }
+
+ if (PREF_CHANGED(NS_NET_PREF_ALWAYSENCODEINUTF8)) {
+ if (GOT_PREF(NS_NET_PREF_ALWAYSENCODEINUTF8, val))
+ gAlwaysEncodeInUTF8 = val;
+ LOG(("encode in UTF-8 %s\n", gAlwaysEncodeInUTF8 ? "enabled" : "disabled"));
+ }
+#undef PREF_CHANGED
+#undef GOT_PREF
+}
+
+#define SHIFT_FROM(name, what) \
+void \
+nsStandardURL::name(int32_t diff) \
+{ \
+ if (!diff) return; \
+ if (what.mLen >= 0) { \
+ CheckedInt<int32_t> pos = what.mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ what.mPos = pos.value(); \
+ }
+
+#define SHIFT_FROM_NEXT(name, what, next) \
+ SHIFT_FROM(name, what) \
+ next(diff); \
+}
+
+#define SHIFT_FROM_LAST(name, what) \
+ SHIFT_FROM(name, what) \
+}
+
+SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
+SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
+SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
+SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
+SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
+SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
+SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
+SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
+SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
+SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
+SHIFT_FROM_LAST(ShiftFromRef, mRef)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsStandardURL)
+NS_IMPL_RELEASE(nsStandardURL)
+
+NS_INTERFACE_MAP_BEGIN(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY(nsIMutable)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
+ NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
+ // see nsStandardURL::Equals
+ if (aIID.Equals(kThisImplCID))
+ foundInterface = static_cast<nsIURI *>(this);
+ else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURI
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpec(nsACString &result)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ result = mSpec;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString &result)
+{
+ result = mSpec;
+ if (mPassword.mLen >= 0) {
+ result.Replace(mPassword.mPos, mPassword.mLen, "****");
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpecIgnoringRef(nsACString &result)
+{
+ // URI without ref is 0 to one char before ref
+ if (mRef.mLen >= 0) {
+ URLSegment noRef(0, mRef.mPos - 1);
+
+ result = Segment(noRef);
+ } else {
+ result = mSpec;
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPrePath(nsACString &result)
+{
+ result = Prepath();
+ return NS_OK;
+}
+
+// result is strictly US-ASCII
+NS_IMETHODIMP
+nsStandardURL::GetScheme(nsACString &result)
+{
+ result = Scheme();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUserPass(nsACString &result)
+{
+ result = Userpass();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUsername(nsACString &result)
+{
+ result = Username();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPassword(nsACString &result)
+{
+ result = Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHostPort(nsACString &result)
+{
+ result = Hostport();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHost(nsACString &result)
+{
+ result = Host();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetPort(int32_t *result)
+{
+ // should never be more than 16 bit
+ MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
+ *result = mPort;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPath(nsACString &result)
+{
+ result = Path();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiSpec(nsACString &result)
+{
+ if (mSpecEncoding == eEncoding_Unknown) {
+ if (IsASCII(mSpec))
+ mSpecEncoding = eEncoding_ASCII;
+ else
+ mSpecEncoding = eEncoding_UTF8;
+ }
+
+ if (mSpecEncoding == eEncoding_ASCII) {
+ result = mSpec;
+ return NS_OK;
+ }
+
+ // try to guess the capacity required for result...
+ result.SetCapacity(mSpec.Length() + std::min<uint32_t>(32, mSpec.Length()/10));
+
+ result = Substring(mSpec, 0, mScheme.mLen + 3);
+
+ // This is left fallible as this entire function is expected to be
+ // infallible.
+ NS_EscapeURL(Userpass(true), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+
+ // get the hostport
+ nsAutoCString hostport;
+ MOZ_ALWAYS_SUCCEEDS(GetAsciiHostPort(hostport));
+ result += hostport;
+
+ // This is left fallible as this entire function is expected to be
+ // infallible.
+ NS_EscapeURL(Path(), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHostPort(nsACString &result)
+{
+ if (mHostEncoding == eEncoding_ASCII) {
+ result = Hostport();
+ return NS_OK;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(GetAsciiHost(result));
+
+ // As our mHostEncoding is not eEncoding_ASCII, we know that
+ // the our host is not ipv6, and we can avoid looking at it.
+ MOZ_ASSERT(result.FindChar(':') == -1, "The host must not be ipv6");
+
+ // hostport = "hostA" + ":port"
+ uint32_t pos = mHost.mPos + mHost.mLen;
+ if (pos < mPath.mPos)
+ result += Substring(mSpec, pos, mPath.mPos - pos);
+
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHost(nsACString &result)
+{
+ if (mHostEncoding == eEncoding_ASCII) {
+ result = Host();
+ return NS_OK;
+ }
+
+ // perhaps we have it cached...
+ if (mHostA) {
+ result = mHostA;
+ return NS_OK;
+ }
+
+ if (gIDN) {
+ nsresult rv;
+ rv = gIDN->ConvertUTF8toACE(Host(), result);
+ if (NS_SUCCEEDED(rv)) {
+ mHostA = ToNewCString(result);
+ return NS_OK;
+ }
+ NS_WARNING("nsIDNService::ConvertUTF8toACE failed");
+ }
+
+ // something went wrong... guess all we can do is URL escape :-/
+ NS_EscapeURL(Host(), esc_OnlyNonASCII | esc_AlwaysCopy, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetOriginCharset(nsACString &result)
+{
+ if (mOriginCharset.IsEmpty())
+ result.AssignLiteral("UTF-8");
+ else
+ result = mOriginCharset;
+ return NS_OK;
+}
+
+static bool
+IsSpecialProtocol(const nsACString &input)
+{
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ nsACString::const_iterator iterator(start);
+ input.EndReading(end);
+
+ while (iterator != end && *iterator != ':') {
+ iterator++;
+ }
+
+ nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
+
+ return protocol.LowerCaseEqualsLiteral("http") ||
+ protocol.LowerCaseEqualsLiteral("https") ||
+ protocol.LowerCaseEqualsLiteral("ftp") ||
+ protocol.LowerCaseEqualsLiteral("ws") ||
+ protocol.LowerCaseEqualsLiteral("wss") ||
+ protocol.LowerCaseEqualsLiteral("file") ||
+ protocol.LowerCaseEqualsLiteral("gopher");
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetSpec(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
+
+ if (input.Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI;
+ net_FilterURIString(flat, filteredURI);
+
+ if (filteredURI.Length() == 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Make a backup of the curent URL
+ nsStandardURL prevURL(false,false);
+ prevURL.CopyMembers(this, eHonorRef, EmptyCString());
+ Clear();
+
+ if (IsSpecialProtocol(filteredURI)) {
+ // Bug 652186: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ nsAutoCString::iterator start;
+ nsAutoCString::iterator end;
+ filteredURI.BeginWriting(start);
+ filteredURI.EndWriting(end);
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ const char *spec = filteredURI.get();
+ int32_t specLength = filteredURI.Length();
+
+ // parse the given URL...
+ nsresult rv = ParseURL(spec, specLength);
+ if (NS_SUCCEEDED(rv)) {
+ // finally, use the URLSegment member variables to build a normalized
+ // copy of |spec|
+ rv = BuildNormalizedSpec(spec);
+ }
+
+ if (NS_FAILED(rv)) {
+ Clear();
+ // If parsing the spec has failed, restore the old URL
+ // so we don't end up with an empty URL.
+ CopyMembers(&prevURL, eHonorRef, EmptyCString());
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG((" spec = %s\n", mSpec.get()));
+ LOG((" port = %d\n", mPort));
+ LOG((" scheme = (%u,%d)\n", mScheme.mPos, mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
+ LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen));
+ LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
+ LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen));
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetScheme(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &scheme = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
+
+ if (scheme.IsEmpty()) {
+ NS_WARNING("cannot remove the scheme from an url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mScheme.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Scheme().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
+
+ if (shift) {
+ mScheme.mLen = scheme.Length();
+ ShiftFromAuthority(shift);
+ }
+
+ // ensure new scheme is lowercase
+ //
+ // XXX the string code unfortunately doesn't provide a ToLowerCase
+ // that operates on a substring.
+ net_ToLowerCase((char *) mSpec.get(), mScheme.mLen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetUserPass(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &userpass = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (userpass.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set user:pass on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mAuthority.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mSpec.Length() + input.Length() - Userpass(true).Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (userpass.IsEmpty()) {
+ // remove user:pass
+ if (mUsername.mLen > 0) {
+ if (mPassword.mLen > 0)
+ mUsername.mLen += (mPassword.mLen + 1);
+ mUsername.mLen++;
+ mSpec.Cut(mUsername.mPos, mUsername.mLen);
+ mAuthority.mLen -= mUsername.mLen;
+ ShiftFromHost(-mUsername.mLen);
+ mUsername.mLen = -1;
+ mPassword.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
+
+ nsresult rv;
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+
+ rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(),
+ &usernamePos, &usernameLen,
+ &passwordPos, &passwordLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // build new user:pass in |buf|
+ nsAutoCString buf;
+ if (usernameLen > 0) {
+ GET_SEGMENT_ENCODER(encoder);
+ bool ignoredOut;
+ usernameLen = encoder.EncodeSegmentCount(userpass.get(),
+ URLSegment(usernamePos,
+ usernameLen),
+ esc_Username | esc_AlwaysCopy,
+ buf, ignoredOut);
+ if (passwordLen >= 0) {
+ buf.Append(':');
+ passwordLen = encoder.EncodeSegmentCount(userpass.get(),
+ URLSegment(passwordPos,
+ passwordLen),
+ esc_Password |
+ esc_AlwaysCopy, buf,
+ ignoredOut);
+ }
+ if (mUsername.mLen < 0)
+ buf.Append('@');
+ }
+
+ uint32_t shift = 0;
+
+ if (mUsername.mLen < 0) {
+ // no existing user:pass
+ if (!buf.IsEmpty()) {
+ mSpec.Insert(buf, mHost.mPos);
+ mUsername.mPos = mHost.mPos;
+ shift = buf.Length();
+ }
+ }
+ else {
+ // replace existing user:pass
+ uint32_t userpassLen = mUsername.mLen;
+ if (mPassword.mLen >= 0)
+ userpassLen += (mPassword.mLen + 1);
+ mSpec.Replace(mUsername.mPos, userpassLen, buf);
+ shift = buf.Length() - userpassLen;
+ }
+ if (shift) {
+ ShiftFromHost(shift);
+ mAuthority.mLen += shift;
+ }
+ // update positions and lengths
+ mUsername.mLen = usernameLen;
+ mPassword.mLen = passwordLen;
+ if (passwordLen)
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetUsername(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &username = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (username.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set username on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (username.IsEmpty())
+ return SetUserPass(username);
+
+ if (mSpec.Length() + input.Length() - Username().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ // escape username if necessary
+ nsAutoCString buf;
+ GET_SEGMENT_ENCODER(encoder);
+ const nsACString &escUsername =
+ encoder.EncodeSegment(username, esc_Username, buf);
+
+ int32_t shift;
+
+ if (mUsername.mLen < 0) {
+ mUsername.mPos = mAuthority.mPos;
+ mSpec.Insert(escUsername + NS_LITERAL_CSTRING("@"), mUsername.mPos);
+ shift = escUsername.Length() + 1;
+ }
+ else
+ shift = ReplaceSegment(mUsername.mPos, mUsername.mLen, escUsername);
+
+ if (shift) {
+ mUsername.mLen = escUsername.Length();
+ mAuthority.mLen += shift;
+ ShiftFromPassword(shift);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPassword(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &password = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (password.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set password on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mUsername.mLen <= 0) {
+ NS_WARNING("cannot set password without existing username");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mSpec.Length() + input.Length() - Password().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (password.IsEmpty()) {
+ if (mPassword.mLen >= 0) {
+ // cut(":password")
+ mSpec.Cut(mPassword.mPos - 1, mPassword.mLen + 1);
+ ShiftFromHost(-(mPassword.mLen + 1));
+ mAuthority.mLen -= (mPassword.mLen + 1);
+ mPassword.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // escape password if necessary
+ nsAutoCString buf;
+ GET_SEGMENT_ENCODER(encoder);
+ const nsACString &escPassword =
+ encoder.EncodeSegment(password, esc_Password, buf);
+
+ int32_t shift;
+
+ if (mPassword.mLen < 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ mSpec.Insert(NS_LITERAL_CSTRING(":") + escPassword, mPassword.mPos - 1);
+ shift = escPassword.Length() + 1;
+ }
+ else
+ shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
+
+ if (shift) {
+ mPassword.mLen = escPassword.Length();
+ mAuthority.mLen += shift;
+ ShiftFromHost(shift);
+ }
+ return NS_OK;
+}
+
+void
+nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd)
+{
+ for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
+ nsACString::const_iterator c(aStart);
+ if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
+ aEnd = c;
+ }
+ }
+}
+
+// If aValue only has a host part and no port number, the port
+// will not be reset!!!
+NS_IMETHODIMP
+nsStandardURL::SetHostPort(const nsACString &aValue)
+{
+ ENSURE_MUTABLE();
+
+ // We cannot simply call nsIURI::SetHost because that would treat the name as
+ // an IPv6 address (like http:://[server:443]/). We also cannot call
+ // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
+
+ nsACString::const_iterator start, end;
+ aValue.BeginReading(start);
+ aValue.EndReading(end);
+ nsACString::const_iterator iter(start);
+ bool isIPv6 = false;
+
+ FindHostLimit(start, end);
+
+ if (*start == '[') { // IPv6 address
+ if (!FindCharInReadable(']', iter, end)) {
+ // the ] character is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ // iter now at the ']' character
+ isIPv6 = true;
+ } else {
+ nsACString::const_iterator iter2(start);
+ if (FindCharInReadable(']', iter2, end)) {
+ // if the first char isn't [ then there should be no ] character
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ FindCharInReadable(':', iter, end);
+
+ if (!isIPv6 && iter != end) {
+ nsACString::const_iterator iter2(iter);
+ iter2++; // Skip over the first ':' character
+ if (FindCharInReadable(':', iter2, end)) {
+ // If there is more than one ':' character it suggests an IPv6
+ // The format should be [2001::1]:80 where the port is optional
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ nsresult rv = SetHost(Substring(start, iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Also set the port if needed.
+ if (iter != end) {
+ iter++;
+ if (iter != end) {
+ nsCString portStr(Substring(iter, end));
+ nsresult rv;
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetPort(port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Failure parsing port number
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // port number is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ return NS_OK;
+}
+
+// This function is different than SetHostPort in that the port number will be
+// reset as well if aValue parameter does not contain a port port number.
+NS_IMETHODIMP
+nsStandardURL::SetHostAndPort(const nsACString &aValue)
+{
+ // Reset the port and than call SetHostPort. SetHostPort does not reset
+ // the port number.
+ nsresult rv = SetPort(-1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetHostPort(aValue);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetHost(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &hostname = PromiseFlatCString(input);
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ const nsCString unescapedHost(Substring(start, end));
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(),
+ esc_AlwaysCopy | esc_Host, flat);
+ const char *host = flat.get();
+
+ LOG(("nsStandardURL::SetHost [host=%s]\n", host));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (flat.IsEmpty())
+ return NS_OK;
+ NS_WARNING("cannot set host on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ if (flat.IsEmpty()) {
+ // Setting an empty hostname is not allowed for
+ // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (strlen(host) < flat.Length())
+ return NS_ERROR_MALFORMED_URI; // found embedded null
+
+ // For consistency with SetSpec/nsURLParsers, don't allow spaces
+ // in the hostname.
+ if (strchr(host, ' '))
+ return NS_ERROR_MALFORMED_URI;
+
+ if (mSpec.Length() + strlen(host) - Host().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+ mHostEncoding = eEncoding_ASCII;
+
+ uint32_t len;
+ nsAutoCString hostBuf;
+ nsresult rv = NormalizeIDN(flat, hostBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) {
+ hostBuf = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies if the call was successful
+ host = hostBuf.get();
+ len = hostBuf.Length();
+
+ if (!ValidIPv6orHostname(host, len)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mHost.mLen < 0) {
+ int port_length = 0;
+ if (mPort != -1) {
+ nsAutoCString buf;
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ port_length = buf.Length();
+ }
+ if (mAuthority.mLen > 0) {
+ mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
+ mHost.mLen = 0;
+ } else if (mScheme.mLen > 0) {
+ mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
+ mHost.mLen = 0;
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
+
+ if (shift) {
+ mHost.mLen = len;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+ }
+
+ // Now canonicalize the host to lowercase
+ net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPort(int32_t port)
+{
+ ENSURE_MUTABLE();
+
+ LOG(("nsStandardURL::SetPort [port=%d]\n", port));
+
+ if ((port == mPort) || (mPort == -1 && port == mDefaultPort))
+ return NS_OK;
+
+ // ports must be >= 0 and 16 bit
+ // -1 == use default
+ if (port < -1 || port > std::numeric_limits<uint16_t>::max())
+ return NS_ERROR_MALFORMED_URI;
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ NS_WARNING("cannot set port on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ InvalidateCache();
+ if (port == mDefaultPort) {
+ port = -1;
+ }
+
+ ReplacePortInSpec(port);
+
+ mPort = port;
+ return NS_OK;
+}
+
+/**
+ * Replaces the existing port in mSpec with aNewPort.
+ *
+ * The caller is responsible for:
+ * - Calling InvalidateCache (since our mSpec is changing).
+ * - Checking whether aNewPort is mDefaultPort (in which case the
+ * caller should pass aNewPort=-1).
+ */
+void
+nsStandardURL::ReplacePortInSpec(int32_t aNewPort)
+{
+ MOZ_ASSERT(mMutable, "Caller should ensure we're mutable");
+ NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
+ "Caller should check its passed-in value and pass -1 instead of "
+ "mDefaultPort, to avoid encoding default port into mSpec");
+
+ // Create the (possibly empty) string that we're planning to replace:
+ nsAutoCString buf;
+ if (mPort != -1) {
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ }
+ // Find the position & length of that string:
+ const uint32_t replacedLen = buf.Length();
+ const uint32_t replacedStart =
+ mAuthority.mPos + mAuthority.mLen - replacedLen;
+
+ // Create the (possibly empty) replacement string:
+ if (aNewPort == -1) {
+ buf.Truncate();
+ } else {
+ buf.Assign(':');
+ buf.AppendInt(aNewPort);
+ }
+ // Perform the replacement:
+ mSpec.Replace(replacedStart, replacedLen, buf);
+
+ // Bookkeeping to reflect the new length:
+ int32_t shift = buf.Length() - replacedLen;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetPath(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &path = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetPath [path=%s]\n", path.get()));
+
+ InvalidateCache();
+
+ if (!path.IsEmpty()) {
+ nsAutoCString spec;
+
+ spec.Assign(mSpec.get(), mPath.mPos);
+ if (path.First() != '/')
+ spec.Append('/');
+ spec.Append(path);
+
+ return SetSpec(spec);
+ }
+ else if (mPath.mLen >= 1) {
+ mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ mQuery.mLen = -1;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Equals(nsIURI *unknownOther, bool *result)
+{
+ return EqualsInternal(unknownOther, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::EqualsExceptRef(nsIURI *unknownOther, bool *result)
+{
+ return EqualsInternal(unknownOther, eIgnoreRef, result);
+}
+
+nsresult
+nsStandardURL::EqualsInternal(nsIURI *unknownOther,
+ nsStandardURL::RefHandlingEnum refHandlingMode,
+ bool *result)
+{
+ NS_ENSURE_ARG_POINTER(unknownOther);
+ NS_PRECONDITION(result, "null pointer");
+
+ RefPtr<nsStandardURL> other;
+ nsresult rv = unknownOther->QueryInterface(kThisImplCID,
+ getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // First, check whether one URIs is an nsIFileURL while the other
+ // is not. If that's the case, they're different.
+ if (mSupportsFileURL != other->mSupportsFileURL) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Next check parts of a URI that, if different, automatically make the
+ // URIs different
+ if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
+ // Check for host manually, since conversion to file will
+ // ignore the host!
+ !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
+ !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
+ !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
+ !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
+ Port() != other->Port()) {
+ // No need to compare files or other URI parts -- these are different
+ // beasties
+ *result = false;
+ return NS_OK;
+ }
+
+ if (refHandlingMode == eHonorRef &&
+ !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Then check for exact identity of URIs. If we have it, they're equal
+ if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
+ SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
+ SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // At this point, the URIs are not identical, but they only differ in the
+ // directory/filename/extension. If these are file URLs, then get the
+ // corresponding file objects and compare those, since two filenames that
+ // differ, eg, only in case could still be equal.
+ if (mSupportsFileURL) {
+ // Assume not equal for failure cases... but failures in GetFile are
+ // really failures, more or less, so propagate them to caller.
+ *result = false;
+
+ rv = EnsureFile();
+ nsresult rv2 = other->EnsureFile();
+ // special case for resource:// urls that don't resolve to files
+ if (rv == NS_ERROR_NO_INTERFACE && rv == rv2)
+ return NS_OK;
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
+ this, mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(mFile, "EnsureFile() lied!");
+ rv = rv2;
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure file",
+ other.get(), other->mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(other->mFile, "EnsureFile() lied!");
+ return mFile->Equals(other->mFile, result);
+ }
+
+ // The URLs are not identical, and they do not correspond to the
+ // same file, so they are different.
+ *result = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SchemeIs(const char *scheme, bool *result)
+{
+ NS_PRECONDITION(result, "null pointer");
+
+ *result = SegmentIs(mScheme, scheme);
+ return NS_OK;
+}
+
+/* virtual */ nsStandardURL*
+nsStandardURL::StartClone()
+{
+ nsStandardURL *clone = new nsStandardURL();
+ return clone;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Clone(nsIURI **result)
+{
+ return CloneInternal(eHonorRef, EmptyCString(), result);
+}
+
+
+NS_IMETHODIMP
+nsStandardURL::CloneIgnoringRef(nsIURI **result)
+{
+ return CloneInternal(eIgnoreRef, EmptyCString(), result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::CloneWithNewRef(const nsACString& newRef, nsIURI **result)
+{
+ return CloneInternal(eReplaceRef, newRef, result);
+}
+
+nsresult
+nsStandardURL::CloneInternal(nsStandardURL::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIURI **result)
+
+{
+ RefPtr<nsStandardURL> clone = StartClone();
+ if (!clone)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Copy local members into clone.
+ // Also copies the cached members mFile, mHostA
+ clone->CopyMembers(this, refHandlingMode, newRef, true);
+
+ clone.forget(result);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CopyMembers(nsStandardURL * source,
+ nsStandardURL::RefHandlingEnum refHandlingMode, const nsACString& newRef,
+ bool copyCached)
+{
+ mSpec = source->mSpec;
+ mDefaultPort = source->mDefaultPort;
+ mPort = source->mPort;
+ mScheme = source->mScheme;
+ mAuthority = source->mAuthority;
+ mUsername = source->mUsername;
+ mPassword = source->mPassword;
+ mHost = source->mHost;
+ mPath = source->mPath;
+ mFilepath = source->mFilepath;
+ mDirectory = source->mDirectory;
+ mBasename = source->mBasename;
+ mExtension = source->mExtension;
+ mQuery = source->mQuery;
+ mRef = source->mRef;
+ mOriginCharset = source->mOriginCharset;
+ mURLType = source->mURLType;
+ mParser = source->mParser;
+ mMutable = true;
+ mSupportsFileURL = source->mSupportsFileURL;
+ mHostEncoding = source->mHostEncoding;
+
+ if (copyCached) {
+ mFile = source->mFile;
+ mHostA = source->mHostA ? strdup(source->mHostA) : nullptr;
+ mSpecEncoding = source->mSpecEncoding;
+ } else {
+ // The same state as after calling InvalidateCache()
+ mFile = nullptr;
+ mHostA = nullptr;
+ mSpecEncoding = eEncoding_Unknown;
+ }
+
+ if (refHandlingMode == eIgnoreRef) {
+ SetRef(EmptyCString());
+ } else if (refHandlingMode == eReplaceRef) {
+ SetRef(newRef);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Resolve(const nsACString &in, nsACString &out)
+{
+ const nsPromiseFlatCString &flat = PromiseFlatCString(in);
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString buf;
+ net_FilterURIString(flat, buf);
+
+ const char *relpath = buf.get();
+ int32_t relpathLen = buf.Length();
+
+ char *result = nullptr;
+
+ LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n",
+ this, mSpec.get(), relpath));
+
+ NS_ASSERTION(mParser, "no parser: unitialized");
+
+ // NOTE: there is no need for this function to produce normalized
+ // output. normalization will occur when the result is used to
+ // initialize a nsStandardURL object.
+
+ if (mScheme.mLen < 0) {
+ NS_WARNING("unable to Resolve URL: this URL not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ URLSegment scheme;
+ char *resultPath = nullptr;
+ bool relative = false;
+ uint32_t offset = 0;
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+
+ // relative urls should never contain a host, so we always want to use
+ // the noauth url parser.
+ // use it to extract a possible scheme
+ rv = mParser->ParseURL(relpath,
+ relpathLen,
+ &scheme.mPos, &scheme.mLen,
+ nullptr, nullptr,
+ nullptr, nullptr);
+
+ // if the parser fails (for example because there is no valid scheme)
+ // reset the scheme and assume a relative url
+ if (NS_FAILED(rv)) scheme.Reset();
+
+ nsAutoCString protocol(Segment(scheme));
+ nsAutoCString baseProtocol(Scheme());
+
+ // We need to do backslash replacement for the following cases:
+ // 1. The input is an absolute path with a http/https/ftp scheme
+ // 2. The input is a relative path, and the base URL has a http/https/ftp scheme
+ if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
+ IsSpecialProtocol(protocol)) {
+
+ nsAutoCString::iterator start;
+ nsAutoCString::iterator end;
+ buf.BeginWriting(start);
+ buf.EndWriting(end);
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ if (scheme.mLen >= 0) {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(relpath, scheme, "ftp", true)) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+
+ }
+ // this URL appears to be absolute
+ // but try to find out more
+ if (SegmentIs(mScheme, relpath, scheme, true)) {
+ // mScheme and Scheme are the same
+ // but this can still be relative
+ if (nsCRT::strncmp(relpath + scheme.mPos + scheme.mLen,
+ "://",3) == 0) {
+ // now this is really absolute
+ // because a :// follows the scheme
+ result = NS_strdup(relpath);
+ } else {
+ // This is a deprecated form of relative urls like
+ // http:file or http:/path/file
+ // we will support it for now ...
+ relative = true;
+ offset = scheme.mLen + 1;
+ }
+ } else {
+ // the schemes are not the same, we are also done
+ // because we have to assume this is absolute
+ result = NS_strdup(relpath);
+ }
+ } else {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(mScheme,"ftp")) {
+ coalesceFlag = (netCoalesceFlags) (coalesceFlag
+ | NET_COALESCE_ALLOW_RELATIVE_ROOT
+ | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ if (relpath[0] == '/' && relpath[1] == '/') {
+ // this URL //host/path is almost absolute
+ result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
+ } else {
+ // then it must be relative
+ relative = true;
+ }
+ }
+ if (relative) {
+ uint32_t len = 0;
+ const char *realrelpath = relpath + offset;
+ switch (*realrelpath) {
+ case '/':
+ // overwrite everything after the authority
+ len = mAuthority.mPos + mAuthority.mLen;
+ break;
+ case '?':
+ // overwrite the existing ?query and #ref
+ if (mQuery.mLen >= 0)
+ len = mQuery.mPos - 1;
+ else if (mRef.mLen >= 0)
+ len = mRef.mPos - 1;
+ else
+ len = mPath.mPos + mPath.mLen;
+ break;
+ case '#':
+ case '\0':
+ // overwrite the existing #ref
+ if (mRef.mLen < 0)
+ len = mPath.mPos + mPath.mLen;
+ else
+ len = mRef.mPos - 1;
+ break;
+ default:
+ if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ if (Filename().Equals(NS_LITERAL_CSTRING("%2F"),
+ nsCaseInsensitiveCStringComparator())) {
+ // if ftp URL ends with %2F then simply
+ // append relative part because %2F also
+ // marks the root directory with ftp-urls
+ len = mFilepath.mPos + mFilepath.mLen;
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ }
+ result = AppendToSubstring(0, len, realrelpath);
+ // locate result path
+ resultPath = result + mPath.mPos;
+ }
+ if (!result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (resultPath)
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ else {
+ // locate result path
+ resultPath = PL_strstr(result, "://");
+ if (resultPath) {
+ resultPath = PL_strchr(resultPath + 3, '/');
+ if (resultPath)
+ net_CoalesceDirs(coalesceFlag,resultPath);
+ }
+ }
+ out.Adopt(result);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetCommonBaseSpec(nsIURI *uri2, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ // if uri's are equal, then return uri as is
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
+ return GetSpec(aResult);
+
+ aResult.Truncate();
+
+ // check pre-path; if they don't match, then return empty string
+ nsStandardURL *stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
+ isEquals = NS_SUCCEEDED(rv)
+ && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
+ && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
+ && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
+ && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
+ && (Port() == stdurl2->Port());
+ if (!isEquals)
+ {
+ if (NS_SUCCEEDED(rv))
+ NS_RELEASE(stdurl2);
+ return NS_OK;
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+ while ((*thisIndex == *thatIndex) && *thisIndex)
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches which include '?' and '#'
+ while ((thisIndex != startCharPos) && (*(thisIndex-1) != '/'))
+ thisIndex--;
+
+ // grab spec from beginning to thisIndex
+ aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
+
+ NS_RELEASE(stdurl2);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetRelativeSpec(nsIURI *uri2, nsACString &aResult)
+{
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ aResult.Truncate();
+
+ // if uri's are equal, then return empty string
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
+ return NS_OK;
+
+ nsStandardURL *stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
+ isEquals = NS_SUCCEEDED(rv)
+ && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
+ && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
+ && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
+ && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
+ && (Port() == stdurl2->Port());
+ if (!isEquals)
+ {
+ if (NS_SUCCEEDED(rv))
+ NS_RELEASE(stdurl2);
+
+ return uri2->GetSpec(aResult);
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+
+#ifdef XP_WIN
+ bool isFileScheme = SegmentIs(mScheme, "file");
+ if (isFileScheme)
+ {
+ // on windows, we need to match the first segment of the path
+ // if these don't match then we need to return an absolute path
+ // skip over any leading '/' in path
+ while ((*thisIndex == *thatIndex) && (*thisIndex == '/'))
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+ // look for end of first segment
+ while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/'))
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // if we didn't match through the first segment, return absolute path
+ if ((*thisIndex != '/') || (*thatIndex != '/'))
+ {
+ NS_RELEASE(stdurl2);
+ return uri2->GetSpec(aResult);
+ }
+ }
+#endif
+
+ while ((*thisIndex == *thatIndex) && *thisIndex)
+ {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches with '#' and '?'
+ while ((*(thatIndex-1) != '/') && (thatIndex != startCharPos))
+ thatIndex--;
+
+ const char *limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;
+
+ // need to account for slashes and add corresponding "../"
+ for (; thisIndex <= limit && *thisIndex; ++thisIndex)
+ {
+ if (*thisIndex == '/')
+ aResult.AppendLiteral("../");
+ }
+
+ // grab spec from thisIndex to end
+ uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
+ aResult.Append(Substring(stdurl2->mSpec, startPos,
+ stdurl2->mSpec.Length() - startPos));
+
+ NS_RELEASE(stdurl2);
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURL
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFilePath(nsACString &result)
+{
+ result = Filepath();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetQuery(nsACString &result)
+{
+ result = Query();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetRef(nsACString &result)
+{
+ result = Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasRef(bool *result)
+{
+ *result = (mRef.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDirectory(nsACString &result)
+{
+ result = Directory();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileName(nsACString &result)
+{
+ result = Filename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileBaseName(nsACString &result)
+{
+ result = Basename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileExtension(nsACString &result)
+{
+ result = Extension();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFilePath(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *filepath = flat.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+
+ // if there isn't a filepath, then there can't be anything
+ // after the path either. this url is likely uninitialized.
+ if (mFilepath.mLen < 0)
+ return SetPath(flat);
+
+ if (filepath && *filepath) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ rv = mParser->ParseFilePath(filepath, flat.Length(),
+ &dirPos, &dirLen,
+ &basePos, &baseLen,
+ &extPos, &extLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // build up new candidate spec
+ spec.Assign(mSpec.get(), mPath.mPos);
+
+ // ensure leading '/'
+ if (filepath[dirPos] != '/')
+ spec.Append('/');
+
+ GET_SEGMENT_ENCODER(encoder);
+
+ // append encoded filepath components
+ if (dirLen > 0)
+ encoder.EncodeSegment(Substring(filepath + dirPos,
+ filepath + dirPos + dirLen),
+ esc_Directory | esc_AlwaysCopy, spec);
+ if (baseLen > 0)
+ encoder.EncodeSegment(Substring(filepath + basePos,
+ filepath + basePos + baseLen),
+ esc_FileBaseName | esc_AlwaysCopy, spec);
+ if (extLen >= 0) {
+ spec.Append('.');
+ if (extLen > 0)
+ encoder.EncodeSegment(Substring(filepath + extPos,
+ filepath + extPos + extLen),
+ esc_FileExtension | esc_AlwaysCopy,
+ spec);
+ }
+
+ // compute the ending position of the current filepath
+ if (mFilepath.mLen >= 0) {
+ uint32_t end = mFilepath.mPos + mFilepath.mLen;
+ if (mSpec.Length() > end)
+ spec.Append(mSpec.get() + end, mSpec.Length() - end);
+ }
+
+ return SetSpec(spec);
+ }
+ else if (mPath.mLen > 1) {
+ mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
+ // left shift query, and ref
+ ShiftFromQuery(1 - mFilepath.mLen);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetQuery(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *query = flat.get();
+
+ LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Query().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (!query || !*query) {
+ // remove existing query
+ if (mQuery.mLen >= 0) {
+ // remove query and leading '?'
+ mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
+ ShiftFromRef(-(mQuery.mLen + 1));
+ mPath.mLen -= (mQuery.mLen + 1);
+ mQuery.mPos = 0;
+ mQuery.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ int32_t queryLen = flat.Length();
+ if (query[0] == '?') {
+ query++;
+ queryLen--;
+ }
+
+ if (mQuery.mLen < 0) {
+ if (mRef.mLen < 0)
+ mQuery.mPos = mSpec.Length();
+ else
+ mQuery.mPos = mRef.mPos - 1;
+ mSpec.Insert('?', mQuery.mPos);
+ mQuery.mPos++;
+ mQuery.mLen = 0;
+ // the insertion pushes these out by 1
+ mPath.mLen++;
+ mRef.mPos++;
+ }
+
+ // encode query if necessary
+ nsAutoCString buf;
+ bool encoded;
+ GET_QUERY_ENCODER(encoder);
+ encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query,
+ buf, encoded);
+ if (encoded) {
+ query = buf.get();
+ queryLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
+
+ if (shift) {
+ mQuery.mLen = queryLen;
+ mPath.mLen += shift;
+ ShiftFromRef(shift);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetRef(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *ref = flat.get();
+
+ LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Ref().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (!ref || !*ref) {
+ // remove existing ref
+ if (mRef.mLen >= 0) {
+ // remove ref and leading '#'
+ mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
+ mPath.mLen -= (mRef.mLen + 1);
+ mRef.mPos = 0;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ int32_t refLen = flat.Length();
+ if (ref[0] == '#') {
+ ref++;
+ refLen--;
+ }
+
+ if (mRef.mLen < 0) {
+ mSpec.Append('#');
+ ++mPath.mLen; // Include the # in the path.
+ mRef.mPos = mSpec.Length();
+ mRef.mLen = 0;
+ }
+
+ // If precent encoding is necessary, `ref` will point to `buf`'s content.
+ // `buf` needs to outlive any use of the `ref` pointer.
+ nsAutoCString buf;
+ if (nsContentUtils::EncodeDecodeURLHash()) {
+ // encode ref if necessary
+ bool encoded;
+ GET_SEGMENT_ENCODER(encoder);
+ encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref,
+ buf, encoded);
+ if (encoded) {
+ ref = buf.get();
+ refLen = buf.Length();
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
+ mPath.mLen += shift;
+ mRef.mLen = refLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetDirectory(const nsACString &input)
+{
+ NS_NOTYETIMPLEMENTED("");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileName(const nsACString &input)
+{
+ ENSURE_MUTABLE();
+
+ const nsPromiseFlatCString &flat = PromiseFlatCString(input);
+ const char *filename = flat.get();
+
+ LOG(("nsStandardURL::SetFileName [filename=%s]\n", filename));
+
+ if (mPath.mLen < 0)
+ return SetPath(flat);
+
+ if (mSpec.Length() + input.Length() - Filename().Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t shift = 0;
+
+ if (!(filename && *filename)) {
+ // remove the filename
+ if (mBasename.mLen > 0) {
+ if (mExtension.mLen >= 0)
+ mBasename.mLen += (mExtension.mLen + 1);
+ mSpec.Cut(mBasename.mPos, mBasename.mLen);
+ shift = -mBasename.mLen;
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ }
+ else {
+ nsresult rv;
+ URLSegment basename, extension;
+
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(),
+ &basename.mPos, &basename.mLen,
+ &extension.mPos, &extension.mLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (basename.mLen < 0) {
+ // remove existing filename
+ if (mBasename.mLen >= 0) {
+ uint32_t len = mBasename.mLen;
+ if (mExtension.mLen >= 0)
+ len += (mExtension.mLen + 1);
+ mSpec.Cut(mBasename.mPos, len);
+ shift = -int32_t(len);
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ }
+ else {
+ nsAutoCString newFilename;
+ bool ignoredOut;
+ GET_SEGMENT_ENCODER(encoder);
+ basename.mLen = encoder.EncodeSegmentCount(filename, basename,
+ esc_FileBaseName |
+ esc_AlwaysCopy,
+ newFilename,
+ ignoredOut);
+ if (extension.mLen >= 0) {
+ newFilename.Append('.');
+ extension.mLen = encoder.EncodeSegmentCount(filename, extension,
+ esc_FileExtension |
+ esc_AlwaysCopy,
+ newFilename,
+ ignoredOut);
+ }
+
+ if (mBasename.mLen < 0) {
+ // insert new filename
+ mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
+ mSpec.Insert(newFilename, mBasename.mPos);
+ shift = newFilename.Length();
+ }
+ else {
+ // replace existing filename
+ uint32_t oldLen = uint32_t(mBasename.mLen);
+ if (mExtension.mLen >= 0)
+ oldLen += (mExtension.mLen + 1);
+ mSpec.Replace(mBasename.mPos, oldLen, newFilename);
+ shift = newFilename.Length() - oldLen;
+ }
+
+ mBasename.mLen = basename.mLen;
+ mExtension.mLen = extension.mLen;
+ if (mExtension.mLen >= 0)
+ mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
+ }
+ }
+ if (shift) {
+ ShiftFromQuery(shift);
+ mFilepath.mLen += shift;
+ mPath.mLen += shift;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileBaseName(const nsACString &input)
+{
+ nsAutoCString extension;
+ nsresult rv = GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newFileName(input);
+
+ if (!extension.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(extension);
+ }
+
+ return SetFileName(newFileName);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFileExtension(const nsACString &input)
+{
+ nsAutoCString newFileName;
+ nsresult rv = GetFileBaseName(newFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!input.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(input);
+ }
+
+ return SetFileName(newFileName);
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIFileURL
+//----------------------------------------------------------------------------
+
+nsresult
+nsStandardURL::EnsureFile()
+{
+ NS_PRECONDITION(mSupportsFileURL,
+ "EnsureFile() called on a URL that doesn't support files!");
+ if (mFile) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ // Parse the spec if we don't have a cached result
+ if (mSpec.IsEmpty()) {
+ NS_WARNING("url not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!SegmentIs(mScheme, "file")) {
+ NS_WARNING("not a file URL");
+ return NS_ERROR_FAILURE;
+ }
+
+ return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFile(nsIFile **result)
+{
+ NS_PRECONDITION(mSupportsFileURL,
+ "GetFile() called on a URL that doesn't support files!");
+ nsresult rv = EnsureFile();
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (LOG_ENABLED()) {
+ nsAutoCString path;
+ mFile->GetNativePath(path);
+ LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n",
+ this, mSpec.get(), path.get()));
+ }
+
+ // clone the file, so the caller can modify it.
+ // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
+ // nsIFile returned from this method; but it seems that some folks do
+ // (see bug 161921). until we can be sure that all the consumers are
+ // behaving themselves, we'll stay on the safe side and clone the file.
+ // see bug 212724 about fixing the consumers.
+ return mFile->Clone(result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetFile(nsIFile *file)
+{
+ ENSURE_MUTABLE();
+
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsresult rv;
+ nsAutoCString url;
+
+ rv = net_GetURLSpecFromFile(file, url);
+ if (NS_FAILED(rv)) return rv;
+
+ SetSpec(url);
+
+ rv = Init(mURLType, mDefaultPort, url, nullptr, nullptr);
+
+ // must clone |file| since its value is not guaranteed to remain constant
+ if (NS_SUCCEEDED(rv)) {
+ InvalidateCache();
+ if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
+ NS_WARNING("nsIFile::Clone failed");
+ // failure to clone is not fatal (GetFile will generate mFile)
+ mFile = nullptr;
+ }
+ }
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIStandardURL
+//----------------------------------------------------------------------------
+
+inline bool
+IsUTFCharset(const char *aCharset)
+{
+ return ((aCharset[0] == 'U' || aCharset[0] == 'u') &&
+ (aCharset[1] == 'T' || aCharset[1] == 't') &&
+ (aCharset[2] == 'F' || aCharset[2] == 'f'));
+}
+
+NS_IMETHODIMP
+nsStandardURL::Init(uint32_t urlType,
+ int32_t defaultPort,
+ const nsACString &spec,
+ const char *charset,
+ nsIURI *baseURI)
+{
+ ENSURE_MUTABLE();
+
+ if (spec.Length() > (uint32_t) net_GetURLMaxLength() ||
+ defaultPort > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ switch (urlType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mDefaultPort = defaultPort;
+ mURLType = urlType;
+
+ mOriginCharset.Truncate();
+
+ //if charset override is absent, use UTF8 as url encoding
+ if (charset != nullptr && *charset != '\0' && !IsUTFCharset(charset)) {
+ mOriginCharset = charset;
+ }
+
+ if (baseURI && net_IsAbsoluteURL(spec)) {
+ baseURI = nullptr;
+ }
+
+ if (!baseURI)
+ return SetSpec(spec);
+
+ nsAutoCString buf;
+ nsresult rv = baseURI->Resolve(spec, buf);
+ if (NS_FAILED(rv)) return rv;
+
+ return SetSpec(buf);
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort)
+{
+ ENSURE_MUTABLE();
+
+ InvalidateCache();
+
+ // should never be more than 16 bit
+ if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // If we're already using the new default-port as a custom port, then clear
+ // it off of our mSpec & set mPort to -1, to indicate that we'll be using
+ // the default from now on (which happens to match what we already had).
+ if (mPort == aNewDefaultPort) {
+ ReplacePortInSpec(-1);
+ mPort = -1;
+ }
+ mDefaultPort = aNewDefaultPort;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetMutable(bool *value)
+{
+ *value = mMutable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SetMutable(bool value)
+{
+ NS_ENSURE_ARG(mMutable || !value);
+
+ mMutable = value;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::Read(nsIObjectInputStream *stream)
+{
+ NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host");
+ NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
+ "Shouldn't have spec encoding here");
+
+ nsresult rv;
+
+ uint32_t urlType;
+ rv = stream->Read32(&urlType);
+ if (NS_FAILED(rv)) return rv;
+ mURLType = urlType;
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = stream->Read32((uint32_t *) &mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Read32((uint32_t *) &mDefaultPort);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_ReadOptionalCString(stream, mSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mUsername);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mPassword);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mBasename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ // handle forward compatibility from older serializations that included mParam
+ URLSegment old_param;
+ rv = ReadSegment(stream, old_param);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mQuery);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ReadSegment(stream, mRef);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_ReadOptionalCString(stream, mOriginCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isMutable;
+ rv = stream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ mMutable = isMutable;
+
+ bool supportsFileURL;
+ rv = stream->ReadBoolean(&supportsFileURL);
+ if (NS_FAILED(rv)) return rv;
+ mSupportsFileURL = supportsFileURL;
+
+ uint32_t hostEncoding;
+ rv = stream->Read32(&hostEncoding);
+ if (NS_FAILED(rv)) return rv;
+ if (hostEncoding != eEncoding_ASCII && hostEncoding != eEncoding_UTF8) {
+ NS_WARNING("Unexpected host encoding");
+ return NS_ERROR_UNEXPECTED;
+ }
+ mHostEncoding = hostEncoding;
+
+ // wait until object is set up, then modify path to include the param
+ if (old_param.mLen >= 0) { // note that mLen=0 is ";"
+ // If this wasn't empty, it marks characters between the end of the
+ // file and start of the query - mPath should include the param,
+ // query and ref already. Bump the mFilePath and
+ // directory/basename/extension components to include this.
+ mFilepath.Merge(mSpec, ';', old_param);
+ mDirectory.Merge(mSpec, ';', old_param);
+ mBasename.Merge(mSpec, ';', old_param);
+ mExtension.Merge(mSpec, ';', old_param);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Write(nsIObjectOutputStream *stream)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ nsresult rv;
+
+ rv = stream->Write32(mURLType);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(mPort));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(uint32_t(mDefaultPort));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_WriteOptionalStringZ(stream, mSpec.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mUsername);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mPassword);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mBasename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ // for backwards compatibility since we removed mParam. Note that this will mean that
+ // an older browser will read "" for mParam, and the param(s) will be part of mPath (as they
+ // after the removal of special handling). It only matters if you downgrade a browser to before
+ // the patch.
+ URLSegment empty;
+ rv = WriteSegment(stream, empty);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mQuery);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = WriteSegment(stream, mRef);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_WriteOptionalStringZ(stream, mOriginCharset.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->WriteBoolean(mMutable);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->WriteBoolean(mSupportsFileURL);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stream->Write32(mHostEncoding);
+ if (NS_FAILED(rv)) return rv;
+
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------------
+// nsStandardURL::nsIIPCSerializableURI
+//---------------------------------------------------------------------------
+
+inline
+ipc::StandardURLSegment
+ToIPCSegment(const nsStandardURL::URLSegment& aSegment)
+{
+ return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
+}
+
+inline
+nsStandardURL::URLSegment
+FromIPCSegment(const ipc::StandardURLSegment& aSegment)
+{
+ return nsStandardURL::URLSegment(aSegment.position(), aSegment.length());
+}
+
+void
+nsStandardURL::Serialize(URIParams& aParams)
+{
+ MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
+ "The spec should never be this long, we missed a check.");
+ StandardURLParams params;
+
+ params.urlType() = mURLType;
+ params.port() = mPort;
+ params.defaultPort() = mDefaultPort;
+ params.spec() = mSpec;
+ params.scheme() = ToIPCSegment(mScheme);
+ params.authority() = ToIPCSegment(mAuthority);
+ params.username() = ToIPCSegment(mUsername);
+ params.password() = ToIPCSegment(mPassword);
+ params.host() = ToIPCSegment(mHost);
+ params.path() = ToIPCSegment(mPath);
+ params.filePath() = ToIPCSegment(mFilepath);
+ params.directory() = ToIPCSegment(mDirectory);
+ params.baseName() = ToIPCSegment(mBasename);
+ params.extension() = ToIPCSegment(mExtension);
+ params.query() = ToIPCSegment(mQuery);
+ params.ref() = ToIPCSegment(mRef);
+ params.originCharset() = mOriginCharset;
+ params.isMutable() = !!mMutable;
+ params.supportsFileURL() = !!mSupportsFileURL;
+ params.hostEncoding() = mHostEncoding;
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+
+ aParams = params;
+}
+
+bool
+nsStandardURL::Deserialize(const URIParams& aParams)
+{
+ NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host");
+ NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
+ "Shouldn't have spec encoding here");
+ NS_PRECONDITION(!mFile, "Shouldn't have cached file");
+
+ if (aParams.type() != URIParams::TStandardURLParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const StandardURLParams& params = aParams.get_StandardURLParams();
+
+ mURLType = params.urlType();
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ NS_NOTREACHED("bad urlType");
+ return false;
+ }
+
+ if (params.hostEncoding() != eEncoding_ASCII &&
+ params.hostEncoding() != eEncoding_UTF8) {
+ NS_WARNING("Unexpected host encoding");
+ return false;
+ }
+
+ mPort = params.port();
+ mDefaultPort = params.defaultPort();
+ mSpec = params.spec();
+ mScheme = FromIPCSegment(params.scheme());
+ mAuthority = FromIPCSegment(params.authority());
+ mUsername = FromIPCSegment(params.username());
+ mPassword = FromIPCSegment(params.password());
+ mHost = FromIPCSegment(params.host());
+ mPath = FromIPCSegment(params.path());
+ mFilepath = FromIPCSegment(params.filePath());
+ mDirectory = FromIPCSegment(params.directory());
+ mBasename = FromIPCSegment(params.baseName());
+ mExtension = FromIPCSegment(params.extension());
+ mQuery = FromIPCSegment(params.query());
+ mRef = FromIPCSegment(params.ref());
+ mOriginCharset = params.originCharset();
+ mMutable = params.isMutable();
+ mSupportsFileURL = params.supportsFileURL();
+ mHostEncoding = params.hostEncoding();
+
+ // mSpecEncoding and mHostA are just caches that can be recovered as needed.
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kStandardURLCID;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t
+nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mOriginCharset.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ aMallocSizeOf(mHostA);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mParser
+ // - mFile
+}
+
+size_t
+nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
new file mode 100644
index 000000000..90f7f7db2
--- /dev/null
+++ b/netwerk/base/nsStandardURL.h
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandardURL_h__
+#define nsStandardURL_h__
+
+#include "nsString.h"
+#include "nsISerializable.h"
+#include "nsIFileURL.h"
+#include "nsIStandardURL.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsURLHelper.h"
+#include "nsIClassInfo.h"
+#include "nsISizeOf.h"
+#include "prclist.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIIPCSerializableURI.h"
+#include "nsISensitiveInfoHiddenURI.h"
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+#define DEBUG_DUMP_URLS_AT_SHUTDOWN
+#endif
+
+class nsIBinaryInputStream;
+class nsIBinaryOutputStream;
+class nsIIDNService;
+class nsIPrefBranch;
+class nsIFile;
+class nsIURLParser;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// standard URL implementation
+//-----------------------------------------------------------------------------
+
+class nsStandardURL : public nsIFileURL
+ , public nsIStandardURL
+ , public nsISerializable
+ , public nsIClassInfo
+ , public nsISizeOf
+ , public nsIIPCSerializableURI
+ , public nsISensitiveInfoHiddenURI
+{
+protected:
+ virtual ~nsStandardURL();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURIWITHQUERY
+ NS_DECL_NSIURL
+ NS_DECL_NSIFILEURL
+ NS_DECL_NSISTANDARDURL
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIMUTABLE
+ NS_DECL_NSIIPCSERIALIZABLEURI
+ NS_DECL_NSISENSITIVEINFOHIDDENURI
+
+ // nsISizeOf
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true);
+
+ static void InitGlobalObjects();
+ static void ShutdownGlobalObjects();
+
+public: /* internal -- HPUX compiler can't handle this being private */
+ //
+ // location and length of an url segment relative to mSpec
+ //
+ struct URLSegment
+ {
+ uint32_t mPos;
+ int32_t mLen;
+
+ URLSegment() : mPos(0), mLen(-1) {}
+ URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
+ URLSegment(const URLSegment& aCopy) : mPos(aCopy.mPos), mLen(aCopy.mLen) {}
+ void Reset() { mPos = 0; mLen = -1; }
+ // Merge another segment following this one to it if they're contiguous
+ // Assumes we have something like "foo;bar" where this object is 'foo' and right
+ // is 'bar'.
+ void Merge(const nsCString &spec, const char separator, const URLSegment &right) {
+ if (mLen >= 0 &&
+ *(spec.get() + mPos + mLen) == separator &&
+ mPos + mLen + 1 == right.mPos) {
+ mLen += 1 + right.mLen;
+ }
+ }
+ };
+
+ //
+ // Pref observer
+ //
+ class nsPrefObserver final : public nsIObserver
+ {
+ ~nsPrefObserver() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsPrefObserver() { }
+ };
+ friend class nsPrefObserver;
+
+ //
+ // URL segment encoder : performs charset conversion and URL escaping.
+ //
+ class nsSegmentEncoder
+ {
+ public:
+ explicit nsSegmentEncoder(const char *charset);
+
+ // Encode the given segment if necessary, and return the length of
+ // the encoded segment. The encoded segment is appended to |buf|
+ // if and only if encoding is required.
+ int32_t EncodeSegmentCount(const char *str,
+ const URLSegment &segment,
+ int16_t mask,
+ nsAFlatCString &buf,
+ bool& appended,
+ uint32_t extraLen = 0);
+
+ // Encode the given string if necessary, and return a reference to
+ // the encoded string. Returns a reference to |buf| if encoding
+ // is required. Otherwise, a reference to |str| is returned.
+ const nsACString &EncodeSegment(const nsASingleFragmentCString &str,
+ int16_t mask,
+ nsAFlatCString &buf);
+ private:
+ bool InitUnicodeEncoder();
+
+ const char* mCharset; // Caller should keep this alive for
+ // the life of the segment encoder
+ nsCOMPtr<nsIUnicodeEncoder> mEncoder;
+ };
+ friend class nsSegmentEncoder;
+
+protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals and EqualsExceptRef
+ // NOTE: *not* virtual, because no one needs to override this so far...
+ nsresult EqualsInternal(nsIURI* unknownOther,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ virtual nsStandardURL* StartClone();
+
+ // Helper to share code between Clone methods.
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** aClone);
+ // Helper method that copies member variables from the source StandardURL
+ // if copyCached = true, it will also copy mFile and mHostA
+ nsresult CopyMembers(nsStandardURL * source, RefHandlingEnum mode,
+ const nsACString& newRef,
+ bool copyCached = false);
+
+ // Helper for subclass implementation of GetFile(). Subclasses that map
+ // URIs to files in a special way should implement this method. It should
+ // ensure that our mFile is initialized, if it's possible.
+ // returns NS_ERROR_NO_INTERFACE if the url does not map to a file
+ virtual nsresult EnsureFile();
+
+private:
+ int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; }
+
+ void ReplacePortInSpec(int32_t aNewPort);
+ void Clear();
+ void InvalidateCache(bool invalidateCachedFile = true);
+
+ bool ValidIPv6orHostname(const char *host, uint32_t aLen);
+ static bool IsValidOfBase(unsigned char c, const uint32_t base);
+ static nsresult ParseIPv4Number(nsCString &input, uint32_t &number);
+ static nsresult NormalizeIPv4(const nsCSubstring &host, nsCString &result);
+ nsresult NormalizeIDN(const nsCSubstring &host, nsCString &result);
+ void CoalescePath(netCoalesceFlags coalesceFlag, char *path);
+
+ uint32_t AppendSegmentToBuf(char *, uint32_t, const char *,
+ const URLSegment &input, URLSegment &output,
+ const nsCString *esc=nullptr,
+ bool useEsc = false, int32_t* diff = nullptr);
+ uint32_t AppendToBuf(char *, uint32_t, const char *, uint32_t);
+
+ nsresult BuildNormalizedSpec(const char *spec);
+
+ bool SegmentIs(const URLSegment &s1, const char *val, bool ignoreCase = false);
+ bool SegmentIs(const char* spec, const URLSegment &s1, const char *val, bool ignoreCase = false);
+ bool SegmentIs(const URLSegment &s1, const char *val, const URLSegment &s2, bool ignoreCase = false);
+
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const char *val, uint32_t valLen);
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const nsACString &val);
+
+ nsresult ParseURL(const char *spec, int32_t specLen);
+ nsresult ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen = -1);
+
+ char *AppendToSubstring(uint32_t pos, int32_t len, const char *tail);
+
+ // dependent substring helpers
+ const nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below
+ const nsDependentCSubstring Segment(const URLSegment &s) { return Segment(s.mPos, s.mLen); }
+
+ // dependent substring getters
+ const nsDependentCSubstring Prepath(); // see below
+ const nsDependentCSubstring Scheme() { return Segment(mScheme); }
+ const nsDependentCSubstring Userpass(bool includeDelim = false); // see below
+ const nsDependentCSubstring Username() { return Segment(mUsername); }
+ const nsDependentCSubstring Password() { return Segment(mPassword); }
+ const nsDependentCSubstring Hostport(); // see below
+ const nsDependentCSubstring Host(); // see below
+ const nsDependentCSubstring Path() { return Segment(mPath); }
+ const nsDependentCSubstring Filepath() { return Segment(mFilepath); }
+ const nsDependentCSubstring Directory() { return Segment(mDirectory); }
+ const nsDependentCSubstring Filename(); // see below
+ const nsDependentCSubstring Basename() { return Segment(mBasename); }
+ const nsDependentCSubstring Extension() { return Segment(mExtension); }
+ const nsDependentCSubstring Query() { return Segment(mQuery); }
+ const nsDependentCSubstring Ref() { return Segment(mRef); }
+
+ // shift the URLSegments to the right by diff
+ void ShiftFromAuthority(int32_t diff);
+ void ShiftFromUsername(int32_t diff);
+ void ShiftFromPassword(int32_t diff);
+ void ShiftFromHost(int32_t diff);
+ void ShiftFromPath(int32_t diff);
+ void ShiftFromFilepath(int32_t diff);
+ void ShiftFromDirectory(int32_t diff);
+ void ShiftFromBasename(int32_t diff);
+ void ShiftFromExtension(int32_t diff);
+ void ShiftFromQuery(int32_t diff);
+ void ShiftFromRef(int32_t diff);
+
+ // fastload helper functions
+ nsresult ReadSegment(nsIBinaryInputStream *, URLSegment &);
+ nsresult WriteSegment(nsIBinaryOutputStream *, const URLSegment &);
+
+ static void PrefsChanged(nsIPrefBranch *prefs, const char *pref);
+
+ void FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd);
+
+ // mSpec contains the normalized version of the URL spec (UTF-8 encoded).
+ nsCString mSpec;
+ int32_t mDefaultPort;
+ int32_t mPort;
+
+ // url parts (relative to mSpec)
+ URLSegment mScheme;
+ URLSegment mAuthority;
+ URLSegment mUsername;
+ URLSegment mPassword;
+ URLSegment mHost;
+ URLSegment mPath;
+ URLSegment mFilepath;
+ URLSegment mDirectory;
+ URLSegment mBasename;
+ URLSegment mExtension;
+ URLSegment mQuery;
+ URLSegment mRef;
+
+ nsCString mOriginCharset;
+ nsCOMPtr<nsIURLParser> mParser;
+
+ // mFile is protected so subclasses can access it directly
+protected:
+ nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile
+
+private:
+ char *mHostA; // cached result for nsIURI::GetHostA
+
+ enum {
+ eEncoding_Unknown,
+ eEncoding_ASCII,
+ eEncoding_UTF8
+ };
+
+ uint32_t mHostEncoding : 2; // eEncoding_xxx
+ uint32_t mSpecEncoding : 2; // eEncoding_xxx
+ uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx
+ uint32_t mMutable : 1; // nsIStandardURL::mutable
+ uint32_t mSupportsFileURL : 1; // QI to nsIFileURL?
+
+ // global objects. don't use COMPtr as its destructor will cause a
+ // coredump if we leak it.
+ static nsIIDNService *gIDN;
+ static char gHostLimitDigits[];
+ static bool gInitialized;
+ static bool gEscapeUTF8;
+ static bool gAlwaysEncodeInUTF8;
+ static bool gEncodeQueryInUTF8;
+
+public:
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ PRCList mDebugCList;
+ void PrintSpec() const { printf(" %s\n", mSpec.get()); }
+#endif
+};
+
+#define NS_THIS_STANDARDURL_IMPL_CID \
+{ /* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \
+ 0xb8e3e97b, \
+ 0x1ccd, \
+ 0x4b45, \
+ {0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7} \
+}
+
+//-----------------------------------------------------------------------------
+// Dependent substring getters
+//-----------------------------------------------------------------------------
+
+inline const nsDependentCSubstring
+nsStandardURL::Segment(uint32_t pos, int32_t len)
+{
+ if (len < 0) {
+ pos = 0;
+ len = 0;
+ }
+ return Substring(mSpec, pos, uint32_t(len));
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Prepath()
+{
+ uint32_t len = 0;
+ if (mAuthority.mLen >= 0)
+ len = mAuthority.mPos + mAuthority.mLen;
+ return Substring(mSpec, 0, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Userpass(bool includeDelim)
+{
+ uint32_t pos=0, len=0;
+ // if there is no username, then there can be no password
+ if (mUsername.mLen > 0) {
+ pos = mUsername.mPos;
+ len = mUsername.mLen;
+ if (mPassword.mLen >= 0)
+ len += (mPassword.mLen + 1);
+ if (includeDelim)
+ len++;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Hostport()
+{
+ uint32_t pos=0, len=0;
+ if (mAuthority.mLen > 0) {
+ pos = mHost.mPos;
+ len = mAuthority.mPos + mAuthority.mLen - pos;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Host()
+{
+ uint32_t pos=0, len=0;
+ if (mHost.mLen > 0) {
+ pos = mHost.mPos;
+ len = mHost.mLen;
+ if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') {
+ pos++;
+ len -= 2;
+ }
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring
+nsStandardURL::Filename()
+{
+ uint32_t pos=0, len=0;
+ // if there is no basename, then there can be no extension
+ if (mBasename.mLen > 0) {
+ pos = mBasename.mPos;
+ len = mBasename.mLen;
+ if (mExtension.mLen >= 0)
+ len += (mExtension.mLen + 1);
+ }
+ return Substring(mSpec, pos, len);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStandardURL_h__
diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp
new file mode 100644
index 000000000..d88370b2b
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerTee.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerTee,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIStreamListenerTee,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ nsresult rv1 = mListener->OnStartRequest(request, context);
+ nsresult rv2 = NS_OK;
+ if (mObserver)
+ rv2 = mObserver->OnStartRequest(request, context);
+
+ // Preserve NS_SUCCESS_XXX in rv1 in case mObserver didn't throw
+ return (NS_FAILED(rv2) && NS_SUCCEEDED(rv1)) ? rv2 : rv1;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ // it is critical that we close out the input stream tee
+ if (mInputTee) {
+ mInputTee->SetSink(nullptr);
+ mInputTee = nullptr;
+ }
+
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease(mEventTarget, mSink.forget());
+ }
+ else {
+ mSink = nullptr;
+ }
+
+ nsresult rv = mListener->OnStopRequest(request, context, status);
+ if (mObserver)
+ mObserver->OnStopRequest(request, context, status);
+ mObserver = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *input,
+ uint64_t offset,
+ uint32_t count)
+{
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIInputStream> tee;
+ nsresult rv;
+
+ if (!mInputTee) {
+ if (mEventTarget)
+ rv = NS_NewInputStreamTeeAsync(getter_AddRefs(tee), input,
+ mSink, mEventTarget);
+ else
+ rv = NS_NewInputStreamTee(getter_AddRefs(tee), input, mSink);
+ if (NS_FAILED(rv)) return rv;
+
+ mInputTee = do_QueryInterface(tee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // re-initialize the input tee since the input stream may have changed.
+ rv = mInputTee->SetSource(input);
+ if (NS_FAILED(rv)) return rv;
+
+ tee = do_QueryInterface(mInputTee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return mListener->OnDataAvailable(request, context, tee, offset, count);
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::Init(nsIStreamListener *listener,
+ nsIOutputStream *sink,
+ nsIRequestObserver *requestObserver)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_ARG_POINTER(sink);
+ mListener = listener;
+ mSink = sink;
+ mObserver = requestObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::InitAsync(nsIStreamListener *listener,
+ nsIEventTarget *eventTarget,
+ nsIOutputStream *sink,
+ nsIRequestObserver *requestObserver)
+{
+ NS_ENSURE_ARG_POINTER(eventTarget);
+ mEventTarget = eventTarget;
+ return Init(listener, sink, requestObserver);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerTee.h b/netwerk/base/nsStreamListenerTee.h
new file mode 100644
index 000000000..d6a6f23a6
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerTee_h__
+#define nsStreamListenerTee_h__
+
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIInputStreamTee.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+
+namespace mozilla {
+namespace net {
+
+class nsStreamListenerTee : public nsIStreamListenerTee
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSISTREAMLISTENERTEE
+
+ nsStreamListenerTee() { }
+
+private:
+ virtual ~nsStreamListenerTee() { }
+
+ nsCOMPtr<nsIInputStreamTee> mInputTee;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp
new file mode 100644
index 000000000..9273e4558
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.cpp
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerWrapper.h"
+#ifdef DEBUG
+#include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerWrapper,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h
new file mode 100644
index 000000000..36bb93dac
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerWrapper_h__
+#define nsStreamListenerWrapper_h__
+
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsIRequestObserver.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// Wrapper class to make replacement of nsHttpChannel's listener
+// from JavaScript possible. It is workaround for bug 433711 and 682305.
+class nsStreamListenerWrapper final : public nsIStreamListener
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ explicit nsStreamListenerWrapper(nsIStreamListener *listener)
+ : mListener(listener)
+ {
+ MOZ_ASSERT(mListener, "no stream listener specified");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSIREQUESTOBSERVER(mListener)
+ NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener)
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+private:
+ ~nsStreamListenerWrapper() {}
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamListenerWrapper_h__
+
diff --git a/netwerk/base/nsStreamLoader.cpp b/netwerk/base/nsStreamLoader.cpp
new file mode 100644
index 000000000..a73b038a7
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "GeckoProfiler.h"
+
+#include <limits>
+
+namespace mozilla {
+namespace net {
+
+nsStreamLoader::nsStreamLoader()
+ : mData()
+{
+}
+
+nsStreamLoader::~nsStreamLoader()
+{
+}
+
+NS_IMETHODIMP
+nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver,
+ nsIRequestObserver* aRequestObserver)
+{
+ NS_ENSURE_ARG_POINTER(aStreamObserver);
+ mObserver = aStreamObserver;
+ mRequestObserver = aRequestObserver;
+ return NS_OK;
+}
+
+nsresult
+nsStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsStreamLoader* it = new nsStreamLoader();
+ if (it == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes)
+{
+ *aNumBytes = mData.length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::GetRequest(nsIRequest **aRequest)
+{
+ NS_IF_ADDREF(*aRequest = mRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) );
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ if (uint64_t(contentLength) > std::numeric_limits<size_t>::max()) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ mContext = ctxt;
+ if (mRequestObserver) {
+ mRequestObserver->OnStartRequest(request, ctxt);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
+ nsresult aStatus)
+{
+ PROFILER_LABEL("nsStreamLoader", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (mObserver) {
+ // provide nsIStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv = mObserver->OnStreamComplete(this, mContext, aStatus,
+ length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ mContext = nullptr;
+ }
+
+ if (mRequestObserver) {
+ mRequestObserver->OnStopRequest(request, ctxt, aStatus);
+ mRequestObserver = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsStreamLoader::WriteSegmentFun(nsIInputStream *inStr,
+ void *closure,
+ const char *fromSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsStreamLoader *self = (nsStreamLoader *) closure;
+
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *writeCount = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ uint32_t countRead;
+ return inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+}
+
+void
+nsStreamLoader::ReleaseData()
+{
+ mData.clearAndFree();
+}
+
+NS_IMETHODIMP
+nsStreamLoader::CheckListenerChain()
+{
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h
new file mode 100644
index 000000000..671fc441f
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamLoader_h__
+#define nsStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamLoader final : public nsIStreamLoader
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsStreamLoader();
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ ~nsStreamLoader();
+
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIRequestObserver> mRequestObserver;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamLoader_h__
diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp
new file mode 100644
index 000000000..3461480b6
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -0,0 +1,563 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamTransportService.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsNetSegmentUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIPipe.h"
+#include "nsITransport.h"
+#include "nsIObserverService.h"
+#include "nsIThreadPool.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsInputStreamTransport
+//
+// Implements nsIInputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsInputStreamTransport : public nsITransport
+ , public nsIInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIINPUTSTREAM
+
+ nsInputStreamTransport(nsIInputStream *source,
+ uint64_t offset,
+ uint64_t limit,
+ bool closeWhenDone)
+ : mSource(source)
+ , mOffset(offset)
+ , mLimit(limit)
+ , mCloseWhenDone(closeWhenDone)
+ , mFirstTime(true)
+ , mInProgress(false)
+ {
+ }
+
+private:
+ virtual ~nsInputStreamTransport()
+ {
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIInputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mOffset;
+ int64_t mLimit;
+ bool mCloseWhenDone;
+ bool mFirstTime;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress;
+};
+
+NS_IMPL_ISUPPORTS(nsInputStreamTransport,
+ nsITransport,
+ nsIInputStream)
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSource; however, then we would
+ // not be reading mSource on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
+ getter_AddRefs(pipeOut),
+ nonblocking, true,
+ segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(this, pipeOut, target,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*result = mPipeIn);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ // this transport only supports reading!
+ NS_NOTREACHED("nsInputStreamTransport::OpenOutputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target)
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink),
+ sink, target);
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIInputStream **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close()
+{
+ if (mCloseWhenDone)
+ mSource->Close();
+
+ // make additional reads return early...
+ mOffset = mLimit = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Available(uint64_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Read(char *buf, uint32_t count, uint32_t *result)
+{
+ if (mFirstTime) {
+ mFirstTime = false;
+ if (mOffset != 0) {
+ // read from current position if offset equal to max
+ if (mOffset != -1) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSource);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ // reset offset to zero so we can use it to enforce limit
+ mOffset = 0;
+ }
+ }
+
+ // limit amount read
+ uint64_t max = count;
+ if (mLimit != -1) {
+ max = mLimit - mOffset;
+ if (max == 0) {
+ *result = 0;
+ return NS_OK;
+ }
+ }
+
+ if (count > max)
+ count = static_cast<uint32_t>(max);
+
+ nsresult rv = mSource->Read(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink)
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset,
+ mLimit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOutputStreamTransport
+//
+// Implements nsIOutputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsOutputStreamTransport : public nsITransport
+ , public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsOutputStreamTransport(nsIOutputStream *sink,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone)
+ : mSink(sink)
+ , mOffset(offset)
+ , mLimit(limit)
+ , mCloseWhenDone(closeWhenDone)
+ , mFirstTime(true)
+ , mInProgress(false)
+ {
+ }
+
+private:
+ virtual ~nsOutputStreamTransport()
+ {
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIOutputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIOutputStream> mSink;
+ int64_t mOffset;
+ int64_t mLimit;
+ bool mCloseWhenDone;
+ bool mFirstTime;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress;
+};
+
+NS_IMPL_ISUPPORTS(nsOutputStreamTransport,
+ nsITransport,
+ nsIOutputStream)
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsOutputStreamTransport::OpenInputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream **result)
+{
+ // this transport only supports writing!
+ NS_NOTREACHED("nsOutputStreamTransport::OpenInputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::OpenOutputStream(uint32_t flags,
+ uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream **result)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSink; however, then we would
+ // not be writing to mSink on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn),
+ getter_AddRefs(mPipeOut),
+ true, nonblocking,
+ segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(pipeIn, this, target,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*result = mPipeOut);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Close(nsresult reason)
+{
+ if (NS_SUCCEEDED(reason))
+ reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeOut->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::SetEventSink(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target)
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink),
+ sink, target);
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIOutputStream **/
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Close()
+{
+ if (mCloseWhenDone)
+ mSink->Close();
+
+ // make additional writes return early...
+ mOffset = mLimit = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::Write(const char *buf, uint32_t count, uint32_t *result)
+{
+ if (mFirstTime) {
+ mFirstTime = false;
+ if (mOffset != 0) {
+ // write to current position if offset equal to max
+ if (mOffset != -1) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSink);
+ if (seekable)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ // reset offset to zero so we can use it to enforce limit
+ mOffset = 0;
+ }
+ }
+
+ // limit amount written
+ uint64_t max = count;
+ if (mLimit != -1) {
+ max = mLimit - mOffset;
+ if (max == 0) {
+ *result = 0;
+ return NS_OK;
+ }
+ }
+
+ if (count > max)
+ count = static_cast<uint32_t>(max);
+
+ nsresult rv = mSink->Write(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink)
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_WRITING, mOffset,
+ mLimit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::WriteSegments(nsReadSegmentFun reader, void *closure,
+ uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::WriteFrom(nsIInputStream *in, uint32_t count, uint32_t *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsOutputStreamTransport::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsStreamTransportService
+//-----------------------------------------------------------------------------
+
+nsStreamTransportService::~nsStreamTransportService()
+{
+ NS_ASSERTION(!mPool, "thread pool wasn't shutdown");
+}
+
+nsresult
+nsStreamTransportService::Init()
+{
+ mPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+ NS_ENSURE_STATE(mPool);
+
+ // Configure the pool
+ mPool->SetName(NS_LITERAL_CSTRING("StreamTrans"));
+ mPool->SetThreadLimit(25);
+ mPool->SetIdleThreadLimit(1);
+ mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30));
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc)
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamTransportService,
+ nsIStreamTransportService,
+ nsIEventTarget,
+ nsIObserver)
+
+NS_IMETHODIMP
+nsStreamTransportService::DispatchFromScript(nsIRunnable *task, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event(task);
+ return Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task, uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::IsOnCurrentThread(bool *result)
+{
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->IsOnCurrentThread(result);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateInputTransport(nsIInputStream *stream,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone,
+ nsITransport **result)
+{
+ nsInputStreamTransport *trans =
+ new nsInputStreamTransport(stream, offset, limit, closeWhenDone);
+ if (!trans)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = trans);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateOutputTransport(nsIOutputStream *stream,
+ int64_t offset,
+ int64_t limit,
+ bool closeWhenDone,
+ nsITransport **result)
+{
+ nsOutputStreamTransport *trans =
+ new nsOutputStreamTransport(stream, offset, limit, closeWhenDone);
+ if (!trans)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = trans);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
+
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ mIsShutdown = true;
+ }
+
+ if (mPool) {
+ mPool->Shutdown();
+ mPool = nullptr;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h
new file mode 100644
index 000000000..7ffd1d1b9
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamTransportService_h__
+#define nsStreamTransportService_h__
+
+#include "nsIStreamTransportService.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadPool;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamTransportService final : public nsIStreamTransportService
+ , public nsIEventTarget
+ , public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSIOBSERVER
+ using nsIEventTarget::Dispatch;
+
+ nsresult Init();
+
+ nsStreamTransportService() : mShutdownLock("nsStreamTransportService.mShutdownLock"),
+ mIsShutdown(false) {}
+
+private:
+ ~nsStreamTransportService();
+
+ nsCOMPtr<nsIThreadPool> mPool;
+
+ mozilla::Mutex mShutdownLock;
+ bool mIsShutdown;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp
new file mode 100644
index 000000000..e80e885c5
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsSyncStreamListener.h"
+#include "nsIPipe.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+nsresult
+nsSyncStreamListener::Init()
+{
+ return NS_NewPipe(getter_AddRefs(mPipeIn),
+ getter_AddRefs(mPipeOut),
+ nsIOService::gDefaultSegmentSize,
+ UINT32_MAX, // no size limit
+ false,
+ false);
+}
+
+nsresult
+nsSyncStreamListener::WaitForData()
+{
+ mKeepWaiting = true;
+
+ while (mKeepWaiting)
+ NS_ENSURE_STATE(NS_ProcessNextEvent(NS_GetCurrentThread()));
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsSyncStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIInputStream,
+ nsISyncStreamListener)
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISyncStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::GetInputStream(nsIInputStream **result)
+{
+ NS_ADDREF(*result = this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStartRequest(nsIRequest *request,
+ nsISupports *context)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *stream,
+ uint64_t offset,
+ uint32_t count)
+{
+ uint32_t bytesWritten;
+
+ nsresult rv = mPipeOut->WriteFrom(stream, count, &bytesWritten);
+
+ // if we get an error, then return failure. this will cause the
+ // channel to be canceled, and as a result our OnStopRequest method
+ // will be called immediately. because of this we do not need to
+ // set mStatus or mKeepWaiting here.
+ if (NS_FAILED(rv))
+ return rv;
+
+ // we expect that all data will be written to the pipe because
+ // the pipe was created to have "infinite" room.
+ NS_ASSERTION(bytesWritten == count, "did not write all data");
+
+ mKeepWaiting = false; // unblock Read
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStopRequest(nsIRequest *request,
+ nsISupports *context,
+ nsresult status)
+{
+ mStatus = status;
+ mKeepWaiting = false; // unblock Read
+ mDone = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIInputStream
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::Close()
+{
+ mStatus = NS_BASE_STREAM_CLOSED;
+ mDone = true;
+
+ // It'd be nice if we could explicitly cancel the request at this point,
+ // but we don't have a reference to it, so the best we can do is close the
+ // pipe so that the next OnDataAvailable event will fail.
+ if (mPipeIn) {
+ mPipeIn->Close();
+ mPipeIn = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Available(uint64_t *result)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ mStatus = mPipeIn->Available(result);
+ if (NS_SUCCEEDED(mStatus) && (*result == 0) && !mDone) {
+ mStatus = WaitForData();
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = mPipeIn->Available(result);
+ }
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Read(char *buf,
+ uint32_t bufLen,
+ uint32_t *result)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64)))
+ return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)bufLen);
+ mStatus = mPipeIn->Read(buf, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::ReadSegments(nsWriteSegmentFun writer,
+ void *closure,
+ uint32_t count,
+ uint32_t *result)
+{
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64)))
+ return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)count);
+ mStatus = mPipeIn->ReadSegments(writer, closure, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::IsNonBlocking(bool *result)
+{
+ *result = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsSyncStreamListener.h b/netwerk/base/nsSyncStreamListener.h
new file mode 100644
index 000000000..9fa9c58c3
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncStreamListener_h__
+#define nsSyncStreamListener_h__
+
+#include "nsISyncStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class nsSyncStreamListener final : public nsISyncStreamListener
+ , public nsIInputStream
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISYNCSTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAM
+
+ nsSyncStreamListener()
+ : mStatus(NS_OK)
+ , mKeepWaiting(false)
+ , mDone(false) {}
+
+ nsresult Init();
+
+private:
+ ~nsSyncStreamListener() {}
+
+ nsresult WaitForData();
+
+ nsCOMPtr<nsIInputStream> mPipeIn;
+ nsCOMPtr<nsIOutputStream> mPipeOut;
+ nsresult mStatus;
+ bool mKeepWaiting;
+ bool mDone;
+};
+
+#endif // nsSyncStreamListener_h__
diff --git a/netwerk/base/nsTemporaryFileInputStream.cpp b/netwerk/base/nsTemporaryFileInputStream.cpp
new file mode 100644
index 000000000..c7c5b0648
--- /dev/null
+++ b/netwerk/base/nsTemporaryFileInputStream.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTemporaryFileInputStream.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType;
+
+NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream)
+
+nsTemporaryFileInputStream::nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos)
+ : mFileDescOwner(aFileDescOwner),
+ mStartPos(aStartPos),
+ mCurPos(aStartPos),
+ mEndPos(aEndPos),
+ mClosed(false)
+{
+ NS_ASSERTION(aStartPos <= aEndPos, "StartPos should less equal than EndPos!");
+}
+
+nsTemporaryFileInputStream::nsTemporaryFileInputStream()
+ : mStartPos(0),
+ mCurPos(0),
+ mEndPos(0),
+ mClosed(false)
+{
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Available(uint64_t * bytesAvailable)
+{
+ if (mClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ NS_ASSERTION(mCurPos <= mEndPos, "CurPos should less equal than EndPos!");
+
+ *bytesAvailable = mEndPos - mCurPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Read(char* buffer, uint32_t count, uint32_t* bytesRead)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, buffer, count, bytesRead);
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer,
+ void * closure,
+ uint32_t count,
+ uint32_t * result)
+{
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mCurPos <= mEndPos, "bad stream state");
+ *result = 0;
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex());
+ int64_t offset = PR_Seek64(mFileDescOwner->mFD, mCurPos, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // Limit requested count to the amount remaining in our section of the file.
+ count = std::min(count, uint32_t(mEndPos - mCurPos));
+
+ char buf[4096];
+ while (*result < count) {
+ uint32_t bufCount = std::min(count - *result, (uint32_t) sizeof(buf));
+ int32_t bytesRead = PR_Read(mFileDescOwner->mFD, buf, bufCount);
+ if (bytesRead == 0) {
+ mClosed = true;
+ return NS_OK;
+ }
+
+ if (bytesRead < 0) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ int32_t bytesWritten = 0;
+ while (bytesWritten < bytesRead) {
+ uint32_t writerCount = 0;
+ nsresult rv = writer(this, closure, buf + bytesWritten, *result,
+ bytesRead - bytesWritten, &writerCount);
+ if (NS_FAILED(rv) || writerCount == 0) {
+ // nsIInputStream::ReadSegments' contract specifies that errors
+ // from writer are not propagated to ReadSegments' caller.
+ //
+ // If writer fails, leaving bytes still in buf, that's okay: we
+ // only update mCurPos to reflect successful writes, so the call
+ // to PR_Seek64 at the top will restart us at the right spot.
+ return NS_OK;
+ }
+ NS_ASSERTION(writerCount <= (uint32_t) (bytesRead - bytesWritten),
+ "writer should not write more than we asked it to write");
+ bytesWritten += writerCount;
+ *result += writerCount;
+ mCurPos += writerCount;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ switch (aWhence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ aOffset += mStartPos;
+ break;
+
+ case nsISeekableStream::NS_SEEK_CUR:
+ aOffset += mCurPos;
+ break;
+
+ case nsISeekableStream::NS_SEEK_END:
+ aOffset += mEndPos;
+ break;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aOffset < (int64_t)mStartPos || aOffset > (int64_t)mEndPos) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mCurPos = aOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::Tell(int64_t* aPos)
+{
+ if (!aPos) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mStartPos <= mCurPos, "StartPos should less equal than CurPos!");
+ *aPos = mCurPos - mStartPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTemporaryFileInputStream::SetEOF()
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ return Close();
+}
+
+void
+nsTemporaryFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ TemporaryFileInputStreamParams params;
+
+ MutexAutoLock lock(mFileDescOwner->FileMutex());
+ MOZ_ASSERT(mFileDescOwner->mFD);
+ if (!mClosed) {
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFileDescOwner->mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd);
+ NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
+
+ params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
+
+ Close();
+ } else {
+ NS_WARNING("The stream is already closed. "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptorIndex() = UINT32_MAX;
+ }
+ params.startPos() = mCurPos;
+ params.endPos() = mEndPos;
+ aParams = params;
+}
+
+bool
+nsTemporaryFileInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ const TemporaryFileInputStreamParams& params = aParams.get_TemporaryFileInputStreamParams();
+
+ uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
+ FileDescriptor fd;
+ if (fileDescriptorIndex < aFileDescriptors.Length()) {
+ fd = aFileDescriptors[fileDescriptorIndex];
+ NS_WARNING_ASSERTION(fd.IsValid(),
+ "Received an invalid file descriptor!");
+ } else {
+ NS_WARNING("Received a bad file descriptor index!");
+ }
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFileDescOwner = new FileDescOwner(fileDesc);
+ } else {
+ mClosed = true;
+ }
+
+ mStartPos = mCurPos = params.startPos();
+ mEndPos = params.endPos();
+ return true;
+}
+
+Maybe<uint64_t>
+nsTemporaryFileInputStream::ExpectedSerializedLength()
+{
+ return Nothing();
+}
diff --git a/netwerk/base/nsTemporaryFileInputStream.h b/netwerk/base/nsTemporaryFileInputStream.h
new file mode 100644
index 000000000..950b26c29
--- /dev/null
+++ b/netwerk/base/nsTemporaryFileInputStream.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTemporaryFileInputStream_h__
+#define nsTemporaryFileInputStream_h__
+
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISeekableStream.h"
+#include "prio.h"
+
+class nsTemporaryFileInputStream : public nsIInputStream
+ , public nsISeekableStream
+ , public nsIIPCSerializableInputStream
+{
+public:
+ //used to release a PRFileDesc
+ class FileDescOwner
+ {
+ friend class nsTemporaryFileInputStream;
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileDescOwner)
+ explicit FileDescOwner(PRFileDesc* aFD)
+ : mFD(aFD),
+ mMutex("FileDescOwner::mMutex")
+ {
+ MOZ_ASSERT(aFD);
+ }
+ private:
+ ~FileDescOwner()
+ {
+ PR_Close(mFD);
+ }
+ public:
+ mozilla::Mutex& FileMutex() { return mMutex; }
+
+ private:
+ PRFileDesc* mFD;
+ mozilla::Mutex mMutex;
+ };
+
+ nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos);
+ nsTemporaryFileInputStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+private:
+ virtual ~nsTemporaryFileInputStream() { }
+
+ RefPtr<FileDescOwner> mFileDescOwner;
+ uint64_t mStartPos;
+ uint64_t mCurPos;
+ uint64_t mEndPos;
+ bool mClosed;
+};
+
+#endif // nsTemporaryFileInputStream_h__
diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp
new file mode 100644
index 000000000..e29bbfdab
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "nsTransportUtils.h"
+#include "nsITransport.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+class nsTransportStatusEvent;
+
+class nsTransportEventSinkProxy : public nsITransportEventSink
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsTransportEventSinkProxy(nsITransportEventSink *sink,
+ nsIEventTarget *target)
+ : mSink(sink)
+ , mTarget(target)
+ , mLock("nsTransportEventSinkProxy.mLock")
+ , mLastEvent(nullptr)
+ {
+ NS_ADDREF(mSink);
+ }
+
+private:
+ virtual ~nsTransportEventSinkProxy()
+ {
+ // our reference to mSink could be the last, so be sure to release
+ // it on the target thread. otherwise, we could get into trouble.
+ NS_ProxyRelease(mTarget, dont_AddRef(mSink));
+ }
+
+public:
+ nsITransportEventSink *mSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock;
+ nsTransportStatusEvent *mLastEvent;
+};
+
+class nsTransportStatusEvent : public Runnable
+{
+public:
+ nsTransportStatusEvent(nsTransportEventSinkProxy *proxy,
+ nsITransport *transport,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+ : mProxy(proxy)
+ , mTransport(transport)
+ , mStatus(status)
+ , mProgress(progress)
+ , mProgressMax(progressMax)
+ {}
+
+ ~nsTransportStatusEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ // since this event is being handled, we need to clear the proxy's ref.
+ // if not coalescing all, then last event may not equal self!
+ {
+ MutexAutoLock lock(mProxy->mLock);
+ if (mProxy->mLastEvent == this)
+ mProxy->mLastEvent = nullptr;
+ }
+
+ mProxy->mSink->OnTransportStatus(mTransport, mStatus, mProgress,
+ mProgressMax);
+ return NS_OK;
+ }
+
+ RefPtr<nsTransportEventSinkProxy> mProxy;
+
+ // parameters to OnTransportStatus
+ nsCOMPtr<nsITransport> mTransport;
+ nsresult mStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMPL_ISUPPORTS(nsTransportEventSinkProxy, nsITransportEventSink)
+
+NS_IMETHODIMP
+nsTransportEventSinkProxy::OnTransportStatus(nsITransport *transport,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ nsresult rv = NS_OK;
+ RefPtr<nsTransportStatusEvent> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ // try to coalesce events! ;-)
+ if (mLastEvent && (mLastEvent->mStatus == status)) {
+ mLastEvent->mStatus = status;
+ mLastEvent->mProgress = progress;
+ mLastEvent->mProgressMax = progressMax;
+ }
+ else {
+ event = new nsTransportStatusEvent(this, transport, status,
+ progress, progressMax);
+ if (!event)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ mLastEvent = event; // weak ref
+ }
+ }
+ if (event) {
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post transport status event");
+
+ MutexAutoLock lock(mLock); // cleanup.. don't reference anymore!
+ mLastEvent = nullptr;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+net_NewTransportEventSinkProxy(nsITransportEventSink **result,
+ nsITransportEventSink *sink,
+ nsIEventTarget *target)
+{
+ *result = new nsTransportEventSinkProxy(sink, target);
+ if (!*result)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h
new file mode 100644
index 000000000..4256bcad9
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTransportUtils_h__
+#define nsTransportUtils_h__
+
+#include "nsITransport.h"
+
+/**
+ * This function returns a proxy object for a transport event sink instance.
+ * The transport event sink will be called on the thread indicated by the
+ * given event target. Like events are automatically coalesced. This means
+ * that for example if the status value is the same from event to event, and
+ * the previous event has not yet been delivered, then only one event will
+ * be delivered. The progress reported will be that from the second event.
+
+ * Coalescing events can help prevent a backlog of unprocessed transport
+ * events in the case that the target thread is overworked.
+ */
+nsresult
+net_NewTransportEventSinkProxy(nsITransportEventSink **aResult,
+ nsITransportEventSink *aSink,
+ nsIEventTarget *aTarget);
+
+#endif // nsTransportUtils_h__
diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp
new file mode 100644
index 000000000..84f6b8ea5
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -0,0 +1,1613 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsSocketTransport2.h"
+#include "nsUDPSocket.h"
+#include "nsProxyRelease.h"
+#include "nsAutoPtr.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsNetAddr.h"
+#include "nsNetSegmentUtils.h"
+#include "NetworkActivityMonitor.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIPipe.h"
+#include "prerror.h"
+#include "nsThreadUtils.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "NetStatistics.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400;
+static NS_DEFINE_CID(kSocketTransportServiceCID2, NS_SOCKETTRANSPORTSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsUDPSocket:: *nsUDPSocketFunc)(void);
+
+static nsresult
+PostEvent(nsUDPSocket *s, nsUDPSocketFunc func)
+{
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(NewRunnableMethod(s, func), NS_DISPATCH_NORMAL);
+}
+
+static nsresult
+ResolveHost(const nsACString &host, nsIDNSListener *listener)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ return dns->AsyncResolve(host, 0, listener, nullptr,
+ getter_AddRefs(tmpOutstanding));
+
+}
+
+//-----------------------------------------------------------------------------
+
+class SetSocketOptionRunnable : public Runnable
+{
+public:
+ SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt)
+ : mSocket(aSocket)
+ , mOpt(aOpt)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ return mSocket->SetSocketOption(mOpt);
+ }
+
+private:
+ RefPtr<nsUDPSocket> mSocket;
+ PRSocketOptionData mOpt;
+};
+
+//-----------------------------------------------------------------------------
+// nsUDPOutputStream impl
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream)
+
+nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket,
+ PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr)
+ : mSocket(aSocket)
+ , mFD(aFD)
+ , mPrClientAddr(aPrClientAddr)
+ , mIsClosed(false)
+{
+}
+
+nsUDPOutputStream::~nsUDPOutputStream()
+{
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Close()
+{
+ if (mIsClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ mIsClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+ if (mIsClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ *_retval = 0;
+ int32_t count = PR_SendTo(mFD, aBuf, aCount, 0, &mPrClientAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+
+ *_retval = count;
+
+ mSocket->AddOutputBytes(count);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::IsNonBlocking(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPMessage impl
+//-----------------------------------------------------------------------------
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsUDPMessage)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsUDPMessage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsUDPMessage)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPMessage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsobj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsUDPMessage)
+ tmp->mJsobj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsUDPMessage::nsUDPMessage(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData)
+ : mOutputStream(aOutputStream)
+{
+ memcpy(&mAddr, aAddr, sizeof(NetAddr));
+ aData.SwapElements(mData);
+}
+
+nsUDPMessage::~nsUDPMessage()
+{
+ DropJSObjects(this);
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetFromAddr(nsINetAddr * *aFromAddr)
+{
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetData(nsACString & aData)
+{
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetOutputStream(nsIOutputStream * *aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetRawData(JSContext* cx,
+ JS::MutableHandleValue aRawData)
+{
+ if(!mJsobj){
+ mJsobj = dom::Uint8Array::Create(cx, nullptr, mData.Length(), mData.Elements());
+ HoldJSObjects(this);
+ }
+ aRawData.setObject(*mJsobj);
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>&
+nsUDPMessage::GetDataAsTArray()
+{
+ return mData;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket
+//-----------------------------------------------------------------------------
+
+nsUDPSocket::nsUDPSocket()
+ : mLock("nsUDPSocket.mLock")
+ , mFD(nullptr)
+ , mAppId(NECKO_UNKNOWN_APP_ID)
+ , mIsInIsolatedMozBrowserElement(false)
+ , mAttached(false)
+ , mByteReadCount(0)
+ , mByteWriteCount(0)
+{
+ mAddr.raw.family = PR_AF_UNSPEC;
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService)
+ {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(kSocketTransportServiceCID2);
+ }
+
+ mSts = gSocketTransportService;
+ MOZ_COUNT_CTOR(nsUDPSocket);
+}
+
+nsUDPSocket::~nsUDPSocket()
+{
+ CloseSocket();
+ MOZ_COUNT_DTOR(nsUDPSocket);
+}
+
+void
+nsUDPSocket::AddOutputBytes(uint64_t aBytes)
+{
+ mByteWriteCount += aBytes;
+ SaveNetworkStats(false);
+}
+
+void
+nsUDPSocket::OnMsgClose()
+{
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then socket transport service will call our
+ // OnSocketDetached method automatically. Otherwise, we have to call it
+ // (and thus close the socket) manually.
+ if (!mAttached)
+ OnSocketDetached(mFD);
+}
+
+void
+nsUDPSocket::OnMsgAttach()
+{
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition))
+ return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition))
+ {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult
+nsUDPSocket::TryAttach()
+{
+ nsresult rv;
+
+ if (!gSocketTransportService)
+ return NS_ERROR_FAILURE;
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket())
+ {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsUDPSocket::OnMsgAttach);
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// UDPMessageProxy
+//-----------------------------------------------------------------------------
+class UDPMessageProxy final : public nsIUDPMessage
+{
+public:
+ UDPMessageProxy(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData)
+ : mOutputStream(aOutputStream)
+ {
+ memcpy(&mAddr, aAddr, sizeof(mAddr));
+ aData.SwapElements(mData);
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPMESSAGE
+
+private:
+ ~UDPMessageProxy() {}
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(UDPMessageProxy, nsIUDPMessage)
+
+NS_IMETHODIMP
+UDPMessageProxy::GetFromAddr(nsINetAddr * *aFromAddr)
+{
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetData(nsACString & aData)
+{
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>&
+UDPMessageProxy::GetDataAsTArray()
+{
+ return mData;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetRawData(JSContext* cx,
+ JS::MutableHandleValue aRawData)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetOutputStream(nsIOutputStream * *aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+} //anonymous namespace
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void
+nsUDPSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
+{
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL))
+ {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRNetAddr prClientAddr;
+ uint32_t count;
+ // Bug 1252755 - use 9216 bytes to allign with nICEr and transportlayer to
+ // support the maximum size of jumbo frames
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prClientAddr, PR_INTERVAL_NO_WAIT);
+
+ if (count < 1) {
+ NS_WARNING("error of recvfrom on UDP socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+ mByteReadCount += count;
+ SaveNetworkStats(false);
+
+ FallibleTArray<uint8_t> data;
+ if (!data.AppendElements(buff, count, fallible)) {
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+
+ uint32_t segsize = UDP_PACKET_CHUNK_SIZE;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ true, true, segsize, segcount);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr);
+ rv = NS_AsyncCopy(pipeIn, os, mSts,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, UDP_PACKET_CHUNK_SIZE);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NetAddr netAddr;
+ PRNetAddrToNetAddr(&prClientAddr, &netAddr);
+ nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr, pipeOut, data);
+ mListener->OnPacketReceived(this, message);
+}
+
+void
+nsUDPSocket::OnSocketDetached(PRFileDesc *fd)
+{
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition))
+ mCondition = NS_ERROR_ABORT;
+
+ if (mFD)
+ {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ CloseSocket();
+ }
+ SaveNetworkStats(true);
+
+ if (mListener)
+ {
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIUDPSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = mListener.forget();
+ }
+
+ if (listener) {
+ listener->OnStopListening(this, mCondition);
+ NS_ProxyRelease(mListenerTarget, listener.forget());
+ }
+ }
+}
+
+void
+nsUDPSocket::IsLocal(bool *aIsLocal)
+{
+ // If bound to loopback, this UDP socket only accepts local connections.
+ *aIsLocal = mAddr.raw.family == nsINetAddr::FAMILY_LOCAL;
+}
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket)
+
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ NetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+
+ if (aLoopbackOnly)
+ addr.inet.ip = htonl(INADDR_LOOPBACK);
+ else
+ addr.inet.ip = htonl(INADDR_ANY);
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NetAddr addr;
+
+ if (aPort < 0)
+ aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+ addr.inet.ip = prAddr.inet.ip;
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc)
+{
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenUDPSocket(aAddr->raw.family);
+ if (!mFD)
+ {
+ NS_WARNING("unable to create UDP socket");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPrincipal) {
+ mAppId = aPrincipal->GetAppId();
+ mIsInIsolatedMozBrowserElement =
+ aPrincipal->GetIsInIsolatedMozBrowserElement();
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ if (mAppId != NECKO_UNKNOWN_APP_ID) {
+ nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+ GetActiveNetworkInfo(activeNetworkInfo);
+ mActiveNetworkInfo =
+ new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo);
+ }
+#endif
+
+ uint16_t port;
+ if (NS_FAILED(net::GetPort(aAddr, &port))) {
+ NS_WARNING("invalid bind address");
+ goto fail;
+ }
+
+ PRSocketOptionData opt;
+
+ // Linux kernel will sometimes hand out a used port if we bind
+ // to port 0 with SO_REUSEADDR
+ if (port) {
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = addressReuse;
+ PR_SetSocketOption(mFD, &opt);
+ }
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ PRNetAddr addr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &addr);
+ NetAddrToPRNetAddr(aAddr, &addr);
+
+ if (PR_Bind(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ // create proxy via NetworkActivityMonitor
+ NetworkActivityMonitor::AttachIOLayer(mFD);
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ Close();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Connect(const NetAddr *aAddr)
+{
+ UDPSOCKET_LOG(("nsUDPSocket::Connect [this=%p]\n", this));
+
+ NS_ENSURE_ARG(aAddr);
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+ NS_ASSERTION(onSTSThread, "NOT ON STS THREAD");
+ if (!onSTSThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) {
+ NS_WARNING("Cannot PR_Connect");
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the resulting socket address, which may have been updated.
+ PRNetAddr addr;
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS)
+ {
+ NS_WARNING("cannot get socket name");
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Close()
+{
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener)
+ {
+ // Here we want to go directly with closing the socket since some tests
+ // expects this happen synchronously.
+ CloseSocket();
+
+ SaveNetworkStats(true);
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgClose);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetPort(int32_t *aResult)
+{
+ // no need to enter the lock here
+ uint16_t result;
+ nsresult rv = net::GetPort(&mAddr, &result);
+ *aResult = static_cast<int32_t>(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetLocalAddr(nsINetAddr * *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+void
+nsUDPSocket::SaveNetworkStats(bool aEnforce)
+{
+#ifdef MOZ_WIDGET_GONK
+ if (!mActiveNetworkInfo || mAppId == NECKO_UNKNOWN_APP_ID) {
+ return;
+ }
+
+ if (mByteReadCount == 0 && mByteWriteCount == 0) {
+ return;
+ }
+
+ uint64_t total = mByteReadCount + mByteWriteCount;
+ if (aEnforce || total > NETWORK_STATS_THRESHOLD) {
+ // Create the event to save the network statistics.
+ // the event is then dispathed to the main thread.
+ RefPtr<Runnable> event =
+ new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowserElement, mActiveNetworkInfo,
+ mByteReadCount, mByteWriteCount, false);
+ NS_DispatchToMainThread(event);
+
+ // Reset the counters after saving.
+ mByteReadCount = 0;
+ mByteWriteCount = 0;
+ }
+#endif
+}
+
+void
+nsUDPSocket::CloseSocket()
+{
+ if (mFD) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ UDPSOCKET_LOG(("Intentional leak"));
+ } else {
+
+ PRIntervalTime closeStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(mFD);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
+ < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
+ PR_IntervalToMilliseconds(now - closeStarted));
+ }
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetAddress(NetAddr *aResult)
+{
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// SocketListenerProxy
+//-----------------------------------------------------------------------------
+class SocketListenerProxy final : public nsIUDPSocketListener
+{
+ ~SocketListenerProxy() {}
+
+public:
+ explicit SocketListenerProxy(nsIUDPSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>(aListener))
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable
+ {
+ public:
+ OnPacketReceivedRunnable(const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxy,
+ nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+{
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceivedRunnable::Run()
+{
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ nsCOMPtr<nsIUDPMessage> message = new nsUDPMessage(&netAddr,
+ outputStream,
+ data);
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+
+class SocketListenerProxyBackground final : public nsIUDPSocketListener
+{
+ ~SocketListenerProxyBackground() {}
+
+public:
+ explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener)
+ : mListener(aListener)
+ , mTargetThread(do_GetCurrentThread())
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable
+ {
+ public:
+ OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mMessage(aMessage)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable
+ {
+ public:
+ OnStopListeningRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket,
+ nsresult aStatus)
+ : mListener(aListener)
+ , mSocket(aSocket)
+ , mStatus(aStatus)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxyBackground,
+ nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage)
+{
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus)
+{
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceivedRunnable::Run()
+{
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ UDPSOCKET_LOG(("%s [this=%p], len %u", __FUNCTION__, this, data.Length()));
+ nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr,
+ outputStream,
+ data);
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListeningRunnable::Run()
+{
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+
+class PendingSend : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSend(nsUDPSocket *aSocket, uint16_t aPort,
+ FallibleTArray<uint8_t> &aData)
+ : mSocket(aSocket)
+ , mPort(aPort)
+ {
+ mData.SwapElements(aData);
+ }
+
+private:
+ virtual ~PendingSend() {}
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSend::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ uint32_t count;
+ nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(),
+ mData.Length(), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class PendingSendStream : public nsIDNSListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSendStream(nsUDPSocket *aSocket, uint16_t aPort,
+ nsIInputStream *aStream)
+ : mSocket(aSocket)
+ , mPort(aPort)
+ , mStream(aStream) {}
+
+private:
+ virtual ~PendingSendStream() {}
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ nsCOMPtr<nsIInputStream> mStream;
+};
+
+NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSendStream::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class SendRequestRunnable: public Runnable {
+public:
+ SendRequestRunnable(nsUDPSocket *aSocket,
+ const NetAddr &aAddr,
+ FallibleTArray<uint8_t>&& aData)
+ : mSocket(aSocket)
+ , mAddr(aAddr)
+ , mData(Move(aData))
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+private:
+ RefPtr<nsUDPSocket> mSocket;
+ const NetAddr mAddr;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMETHODIMP
+SendRequestRunnable::Run()
+{
+ uint32_t count;
+ mSocket->SendWithAddress(&mAddr, mData.Elements(),
+ mData.Length(), &count);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsUDPSocket::AsyncListen(nsIUDPSocketListener *aListener)
+{
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListenerTarget = NS_GetCurrentThread();
+ if (NS_IsMainThread()) {
+ // PNecko usage
+ mListener = new SocketListenerProxy(aListener);
+ } else {
+ // PBackground usage from media/mtransport
+ mListener = new SocketListenerProxyBackground(aListener);
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Send(const nsACString &aHost, uint16_t aPort,
+ const uint8_t *aData, uint32_t aDataLength,
+ uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, aDataLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIDNSListener> listener = new PendingSend(this, aPort, fallibleArray);
+
+ nsresult rv = ResolveHost(aHost, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = aDataLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddr(nsINetAddr *aAddr, const uint8_t *aData,
+ uint32_t aDataLength, uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NetAddr netAddr;
+ aAddr->GetNetAddr(&netAddr);
+ return SendWithAddress(&netAddr, aData, aDataLength, _retval);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddress(const NetAddr *aAddr, const uint8_t *aData,
+ uint32_t aDataLength, uint32_t *_retval)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (onSTSThread) {
+ MutexAutoLock lock(mLock);
+ if (!mFD) {
+ // socket is not initialized or has been closed
+ return NS_ERROR_FAILURE;
+ }
+ int32_t count = PR_SendTo(mFD, aData, sizeof(uint8_t) *aDataLength,
+ 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+ this->AddOutputBytes(count);
+ *_retval = count;
+ } else {
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, aDataLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = mSts->Dispatch(
+ new SendRequestRunnable(this, *aAddr, Move(fallibleArray)),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_retval = aDataLength;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStream(const nsACString &aHost, uint16_t aPort,
+ nsIInputStream *aStream)
+{
+ NS_ENSURE_ARG(aStream);
+
+ nsCOMPtr<nsIDNSListener> listener = new PendingSendStream(this, aPort, aStream);
+
+ return ResolveHost(aHost, listener);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStreamWithAddress(const NetAddr *aAddr, nsIInputStream *aStream)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aStream);
+
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prAddr);
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prAddr);
+ return NS_AsyncCopy(aStream, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+}
+
+nsresult
+nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt)
+{
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (!onSTSThread) {
+ // Dispatch to STS thread and re-enter this method there
+ nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt);
+ nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) {
+ UDPSOCKET_LOG(("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, "
+ "error %d\n", this, aOpt.option, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+nsresult
+nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_AddMember;
+ opt.value.add_member.mcaddr = aAddr;
+ opt.value.add_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface)
+{
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+nsresult
+nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_DropMember;
+ opt.value.drop_member.mcaddr = aAddr;
+ opt.value.drop_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastLoopback(bool* aLoopback)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastLoopback(bool aLoopback)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastLoopback;
+ opt.value.mcast_loopback = aLoopback;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetRecvBufferSize(int* size)
+{
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetRecvBufferSize(int size)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetSendBufferSize(int* size)
+{
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetSendBufferSize(int size)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterface(nsACString& aIface)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterface(const nsACString& aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface)
+{
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ NetAddrToPRNetAddr(&aIface, &prIface);
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+nsresult
+nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface)
+{
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastInterface;
+ opt.value.mcast_if = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h
new file mode 100644
index 000000000..4ddff4248
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.h
@@ -0,0 +1,131 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUDPSocket_h__
+#define nsUDPSocket_h__
+
+#include "nsIUDPSocket.h"
+#include "mozilla/Mutex.h"
+#include "nsIOutputStream.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsProxyRelease.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsUDPSocket final : public nsASocketHandler
+ , public nsIUDPSocket
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+
+ uint64_t ByteCountSent() override { return mByteWriteCount; }
+ uint64_t ByteCountReceived() override { return mByteReadCount; }
+
+ void AddOutputBytes(uint64_t aBytes);
+
+ nsUDPSocket();
+
+private:
+ virtual ~nsUDPSocket();
+
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ friend class SetSocketOptionRunnable;
+ nsresult SetSocketOption(const PRSocketOptionData& aOpt);
+ nsresult JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface);
+
+ void SaveNetworkStats(bool aEnforce);
+
+ void CloseSocket();
+
+ // lock protects access to mListener;
+ // so mListener is not cleared while being used/locked.
+ Mutex mLock;
+ PRFileDesc *mFD;
+ NetAddr mAddr;
+ uint32_t mAppId;
+ bool mIsInIsolatedMozBrowserElement;
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ RefPtr<nsSocketTransportService> mSts;
+
+ uint64_t mByteReadCount;
+ uint64_t mByteWriteCount;
+#ifdef MOZ_WIDGET_GONK
+ nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPMessage : public nsIUDPMessage
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsUDPMessage)
+ NS_DECL_NSIUDPMESSAGE
+
+ nsUDPMessage(NetAddr* aAddr,
+ nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>& aData);
+
+private:
+ virtual ~nsUDPMessage();
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+ JS::Heap<JSObject*> mJsobj;
+};
+
+
+//-----------------------------------------------------------------------------
+
+class nsUDPOutputStream : public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsUDPOutputStream(nsUDPSocket* aSocket,
+ PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr);
+
+private:
+ virtual ~nsUDPOutputStream();
+
+ RefPtr<nsUDPSocket> mSocket;
+ PRFileDesc *mFD;
+ PRNetAddr mPrClientAddr;
+ bool mIsClosed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsUDPSocket_h__
diff --git a/netwerk/base/nsURIHashKey.h b/netwerk/base/nsURIHashKey.h
new file mode 100644
index 000000000..520d6c6fa
--- /dev/null
+++ b/netwerk/base/nsURIHashKey.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsURIHashKey_h__
+#define nsURIHashKey_h__
+
+#include "PLDHashTable.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsHashKeys.h"
+#include "mozilla/Unused.h"
+
+/**
+ * Hashtable key class to use with nsTHashtable/nsBaseHashtable
+ */
+class nsURIHashKey : public PLDHashEntryHdr
+{
+public:
+ typedef nsIURI* KeyType;
+ typedef const nsIURI* KeyTypePointer;
+
+ explicit nsURIHashKey(const nsIURI* aKey) :
+ mKey(const_cast<nsIURI*>(aKey)) { MOZ_COUNT_CTOR(nsURIHashKey); }
+ nsURIHashKey(const nsURIHashKey& toCopy) :
+ mKey(toCopy.mKey) { MOZ_COUNT_CTOR(nsURIHashKey); }
+ ~nsURIHashKey() { MOZ_COUNT_DTOR(nsURIHashKey); }
+
+ nsIURI* GetKey() const { return mKey; }
+
+ bool KeyEquals(const nsIURI* aKey) const {
+ bool eq;
+ if (!mKey) {
+ return !aKey;
+ }
+ if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) {
+ return eq;
+ }
+ return false;
+ }
+
+ static const nsIURI* KeyToPointer(nsIURI* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const nsIURI* aKey) {
+ if (!aKey) {
+ // If the key is null, return hash for empty string.
+ return mozilla::HashString(EmptyCString());
+ }
+ nsAutoCString spec;
+ // If GetSpec() fails, ignoring the failure and proceeding with an
+ // empty |spec| seems like the best thing to do.
+ mozilla::Unused << const_cast<nsIURI*>(aKey)->GetSpec(spec);
+ return mozilla::HashString(spec);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+protected:
+ nsCOMPtr<nsIURI> mKey;
+};
+
+#endif // nsURIHashKey_h__
diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp
new file mode 100644
index 000000000..8def697da
--- /dev/null
+++ b/netwerk/base/nsURLHelper.cpp
@@ -0,0 +1,1168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/RangedPtr.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "nsURLHelper.h"
+#include "nsIFile.h"
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "mozilla/Preferences.h"
+#include "prnetdb.h"
+#include "mozilla/Tokenizer.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+// Init/Shutdown
+//----------------------------------------------------------------------------
+
+static bool gInitialized = false;
+static nsIURLParser *gNoAuthURLParser = nullptr;
+static nsIURLParser *gAuthURLParser = nullptr;
+static nsIURLParser *gStdURLParser = nullptr;
+static int32_t gMaxLength = 1048576; // Default: 1MB
+
+static void
+InitGlobals()
+{
+ nsCOMPtr<nsIURLParser> parser;
+
+ parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'noauth' url parser");
+ if (parser) {
+ gNoAuthURLParser = parser.get();
+ NS_ADDREF(gNoAuthURLParser);
+ }
+
+ parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'auth' url parser");
+ if (parser) {
+ gAuthURLParser = parser.get();
+ NS_ADDREF(gAuthURLParser);
+ }
+
+ parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'std' url parser");
+ if (parser) {
+ gStdURLParser = parser.get();
+ NS_ADDREF(gStdURLParser);
+ }
+
+ gInitialized = true;
+ Preferences::AddIntVarCache(&gMaxLength,
+ "network.standard-url.max-length", 1048576);
+}
+
+void
+net_ShutdownURLHelper()
+{
+ if (gInitialized) {
+ NS_IF_RELEASE(gNoAuthURLParser);
+ NS_IF_RELEASE(gAuthURLParser);
+ NS_IF_RELEASE(gStdURLParser);
+ gInitialized = false;
+ }
+}
+
+int32_t net_GetURLMaxLength()
+{
+ return gMaxLength;
+}
+
+//----------------------------------------------------------------------------
+// nsIURLParser getters
+//----------------------------------------------------------------------------
+
+nsIURLParser *
+net_GetAuthURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gAuthURLParser;
+}
+
+nsIURLParser *
+net_GetNoAuthURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gNoAuthURLParser;
+}
+
+nsIURLParser *
+net_GetStdURLParser()
+{
+ if (!gInitialized)
+ InitGlobals();
+ return gStdURLParser;
+}
+
+//---------------------------------------------------------------------------
+// GetFileFromURLSpec implementations
+//---------------------------------------------------------------------------
+nsresult
+net_GetURLSpecFromDir(nsIFile *aFile, nsACString &result)
+{
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (escPath.Last() != '/') {
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetURLSpecFromFile(nsIFile *aFile, nsACString &result)
+{
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if this file references a directory, then we need to ensure that the
+ // URL ends with a slash. this is important since it affects the rules
+ // for relative URL resolution when this URL is used as a base URL.
+ // if the file does not exist, then we make no assumption about its type,
+ // and simply leave the URL unmodified.
+ if (escPath.Last() != '/') {
+ bool dir;
+ rv = aFile->IsDirectory(&dir);
+ if (NS_SUCCEEDED(rv) && dir)
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// file:// URL parsing
+//----------------------------------------------------------------------------
+
+nsresult
+net_ParseFileURL(const nsACString &inURL,
+ nsACString &outDirectory,
+ nsACString &outFileBaseName,
+ nsACString &outFileExtension)
+{
+ nsresult rv;
+
+ if (inURL.Length() > (uint32_t) gMaxLength) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ outDirectory.Truncate();
+ outFileBaseName.Truncate();
+ outFileExtension.Truncate();
+
+ const nsPromiseFlatCString &flatURL = PromiseFlatCString(inURL);
+ const char *url = flatURL.get();
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(flatURL, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_ERROR("must be a file:// url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsIURLParser *parser = net_GetNoAuthURLParser();
+ NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
+
+ uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos;
+ int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen;
+
+ // invoke the parser to extract the URL path
+ rv = parser->ParseURL(url, flatURL.Length(),
+ nullptr, nullptr, // don't care about scheme
+ nullptr, nullptr, // don't care about authority
+ &pathPos, &pathLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // invoke the parser to extract filepath from the path
+ rv = parser->ParsePath(url + pathPos, pathLen,
+ &filepathPos, &filepathLen,
+ nullptr, nullptr, // don't care about query
+ nullptr, nullptr); // don't care about ref
+ if (NS_FAILED(rv)) return rv;
+
+ filepathPos += pathPos;
+
+ // invoke the parser to extract the directory and filename from filepath
+ rv = parser->ParseFilePath(url + filepathPos, filepathLen,
+ &directoryPos, &directoryLen,
+ &basenamePos, &basenameLen,
+ &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (directoryLen > 0)
+ outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen);
+ if (basenameLen > 0)
+ outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen);
+ if (extensionLen > 0)
+ outFileExtension = Substring(inURL, filepathPos + extensionPos, extensionLen);
+ // since we are using a no-auth url parser, there will never be a host
+ // XXX not strictly true... file://localhost/foo/bar.html is a valid URL
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// path manipulation functions
+//----------------------------------------------------------------------------
+
+// Replace all /./ with a / while resolving URLs
+// But only till #?
+void
+net_CoalesceDirs(netCoalesceFlags flags, char* path)
+{
+ /* Stolen from the old netlib's mkparse.c.
+ *
+ * modifies a url of the form /foo/../foo1 -> /foo1
+ * and /foo/./foo1 -> /foo/foo1
+ * and /foo/foo1/.. -> /foo/
+ */
+ char *fwdPtr = path;
+ char *urlPtr = path;
+ char *lastslash = path;
+ uint32_t traversal = 0;
+ uint32_t special_ftp_len = 0;
+
+ /* Remember if this url is a special ftp one: */
+ if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT)
+ {
+ /* some schemes (for example ftp) have the speciality that
+ the path can begin // or /%2F to mark the root of the
+ servers filesystem, a simple / only marks the root relative
+ to the user loging in. We remember the length of the marker */
+ if (nsCRT::strncasecmp(path,"/%2F",4) == 0)
+ special_ftp_len = 4;
+ else if (nsCRT::strncmp(path,"//",2) == 0 )
+ special_ftp_len = 2;
+ }
+
+ /* find the last slash before # or ? */
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#'); ++fwdPtr)
+ {
+ }
+
+ /* found nothing, but go back one only */
+ /* if there is something to go back to */
+ if (fwdPtr != path && *fwdPtr == '\0')
+ {
+ --fwdPtr;
+ }
+
+ /* search the slash */
+ for(; (fwdPtr != path) &&
+ (*fwdPtr != '/'); --fwdPtr)
+ {
+ }
+ lastslash = fwdPtr;
+ fwdPtr = path;
+
+ /* replace all %2E or %2e with . in the path */
+ /* but stop at lastchar if non null */
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#') &&
+ (*lastslash == '\0' || fwdPtr != lastslash); ++fwdPtr)
+ {
+ if (*fwdPtr == '%' && *(fwdPtr+1) == '2' &&
+ (*(fwdPtr+2) == 'E' || *(fwdPtr+2) == 'e'))
+ {
+ *urlPtr++ = '.';
+ ++fwdPtr;
+ ++fwdPtr;
+ }
+ else
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr)
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+
+ // start again, this time for real
+ fwdPtr = path;
+ urlPtr = path;
+
+ for(; (*fwdPtr != '\0') &&
+ (*fwdPtr != '?') &&
+ (*fwdPtr != '#'); ++fwdPtr)
+ {
+ if (*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '/' )
+ {
+ // remove . followed by slash
+ ++fwdPtr;
+ }
+ else if(*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '.' &&
+ (*(fwdPtr+3) == '/' ||
+ *(fwdPtr+3) == '\0' || // This will take care of
+ *(fwdPtr+3) == '?' || // something like foo/bar/..#sometag
+ *(fwdPtr+3) == '#'))
+ {
+ // remove foo/..
+ // reverse the urlPtr to the previous slash if possible
+ // if url does not allow relative root then drop .. above root
+ // otherwise retain them in the path
+ if(traversal > 0 || !(flags &
+ NET_COALESCE_ALLOW_RELATIVE_ROOT))
+ {
+ if (urlPtr != path)
+ urlPtr--; // we must be going back at least by one
+ for(;*urlPtr != '/' && urlPtr != path; urlPtr--)
+ ; // null body
+ --traversal; // count back
+ // forward the fwdPtr past the ../
+ fwdPtr += 2;
+ // if we have reached the beginning of the path
+ // while searching for the previous / and we remember
+ // that it is an url that begins with /%2F then
+ // advance urlPtr again by 3 chars because /%2F already
+ // marks the root of the path
+ if (urlPtr == path && special_ftp_len > 3)
+ {
+ ++urlPtr;
+ ++urlPtr;
+ ++urlPtr;
+ }
+ // special case if we have reached the end
+ // to preserve the last /
+ if (*fwdPtr == '.' && *(fwdPtr+1) == '\0')
+ ++urlPtr;
+ }
+ else
+ {
+ // there are to much /.. in this path, just copy them instead.
+ // forward the urlPtr past the /.. and copying it
+
+ // However if we remember it is an url that starts with
+ // /%2F and urlPtr just points at the "F" of "/%2F" then do
+ // not overwrite it with the /, just copy .. and move forward
+ // urlPtr.
+ if (special_ftp_len > 3 && urlPtr == path+special_ftp_len-1)
+ ++urlPtr;
+ else
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ else
+ {
+ // count the hierachie, but only if we do not have reached
+ // the root of some special urls with a special root marker
+ if (*fwdPtr == '/' && *(fwdPtr+1) != '.' &&
+ (special_ftp_len != 2 || *(fwdPtr+1) != '/'))
+ traversal++;
+ // copy the url incrementaly
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+
+ /*
+ * Now lets remove trailing . case
+ * /foo/foo1/. -> /foo/foo1/
+ */
+
+ if ((urlPtr > (path+1)) && (*(urlPtr-1) == '.') && (*(urlPtr-2) == '/'))
+ urlPtr--;
+
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr)
+ {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+}
+
+nsresult
+net_ResolveRelativePath(const nsACString &relativePath,
+ const nsACString &basePath,
+ nsACString &result)
+{
+ nsAutoCString name;
+ nsAutoCString path(basePath);
+ bool needsDelim = false;
+
+ if ( !path.IsEmpty() ) {
+ char16_t last = path.Last();
+ needsDelim = !(last == '/');
+ }
+
+ nsACString::const_iterator beg, end;
+ relativePath.BeginReading(beg);
+ relativePath.EndReading(end);
+
+ bool stop = false;
+ char c;
+ for (; !stop; ++beg) {
+ c = (beg == end) ? '\0' : *beg;
+ //printf("%c [name=%s] [path=%s]\n", c, name.get(), path.get());
+ switch (c) {
+ case '\0':
+ case '#':
+ case '?':
+ stop = true;
+ MOZ_FALLTHROUGH;
+ case '/':
+ // delimiter found
+ if (name.EqualsLiteral("..")) {
+ // pop path
+ // If we already have the delim at end, then
+ // skip over that when searching for next one to the left
+ int32_t offset = path.Length() - (needsDelim ? 1 : 2);
+ // First check for errors
+ if (offset < 0 )
+ return NS_ERROR_MALFORMED_URI;
+ int32_t pos = path.RFind("/", false, offset);
+ if (pos >= 0)
+ path.Truncate(pos + 1);
+ else
+ path.Truncate();
+ }
+ else if (name.IsEmpty() || name.EqualsLiteral(".")) {
+ // do nothing
+ }
+ else {
+ // append name to path
+ if (needsDelim)
+ path += '/';
+ path += name;
+ needsDelim = true;
+ }
+ name.Truncate();
+ break;
+
+ default:
+ // append char to name
+ name += c;
+ }
+ }
+ // append anything left on relativePath (e.g. #..., ;..., ?...)
+ if (c != '\0')
+ path += Substring(--beg, end);
+
+ result = path;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// scheme fu
+//----------------------------------------------------------------------------
+
+static bool isAsciiAlpha(char c) {
+ return nsCRT::IsAsciiAlpha(c);
+}
+
+static bool
+net_IsValidSchemeChar(const char aChar)
+{
+ if (nsCRT::IsAsciiAlpha(aChar) || nsCRT::IsAsciiDigit(aChar) ||
+ aChar == '+' || aChar == '.' || aChar == '-') {
+ return true;
+ }
+ return false;
+}
+
+/* Extract URI-Scheme if possible */
+nsresult
+net_ExtractURLScheme(const nsACString &inURI,
+ nsACString& scheme)
+{
+ nsACString::const_iterator start, end;
+ inURI.BeginReading(start);
+ inURI.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t) *start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+ p.Record();
+ if (!p.CheckChar(isAsciiAlpha)) {
+ // First char must be alpha
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+
+ if (!p.CheckChar(':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ p.Claim(scheme);
+ scheme.StripChars("\r\n\t");
+ return NS_OK;
+}
+
+bool
+net_IsValidScheme(const char *scheme, uint32_t schemeLen)
+{
+ // first char must be alpha
+ if (!nsCRT::IsAsciiAlpha(*scheme))
+ return false;
+
+ // nsCStrings may have embedded nulls -- reject those too
+ for (; schemeLen; ++scheme, --schemeLen) {
+ if (!(nsCRT::IsAsciiAlpha(*scheme) ||
+ nsCRT::IsAsciiDigit(*scheme) ||
+ *scheme == '+' ||
+ *scheme == '.' ||
+ *scheme == '-'))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+net_IsAbsoluteURL(const nsACString& uri)
+{
+ nsACString::const_iterator start, end;
+ uri.BeginReading(start);
+ uri.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t) *start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+
+ // First char must be alpha
+ if (!p.CheckChar(isAsciiAlpha)) {
+ return false;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+ if (!p.CheckChar(':')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (!p.CheckChar('/')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (p.CheckChar('/')) {
+ // aSpec is really absolute. Ignore aBaseURI in this case
+ return true;
+ }
+ return false;
+}
+
+void
+net_FilterURIString(const nsACString& input, nsACString& result)
+{
+ const char kCharsToStrip[] = "\r\n\t";
+
+ result.Truncate();
+
+ auto start = input.BeginReading();
+ auto end = input.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ auto newStart = std::find_if(start, end, charFilter);
+ auto newEnd = std::find_if(
+ std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter).base();
+
+ // Check if chars need to be stripped.
+ auto itr = std::find_first_of(
+ newStart, newEnd, std::begin(kCharsToStrip), std::end(kCharsToStrip));
+ const bool needsStrip = itr != newEnd;
+
+ // Just use the passed in string rather than creating new copies if no
+ // changes are necessary.
+ if (newStart == start && newEnd == end && !needsStrip) {
+ result = input;
+ return;
+ }
+
+ result.Assign(Substring(newStart, newEnd));
+ if (needsStrip) {
+ result.StripChars(kCharsToStrip);
+ }
+}
+
+
+#if defined(XP_WIN)
+bool
+net_NormalizeFileURL(const nsACString &aURL, nsCString &aResultBuf)
+{
+ bool writing = false;
+
+ nsACString::const_iterator beginIter, endIter;
+ aURL.BeginReading(beginIter);
+ aURL.EndReading(endIter);
+
+ const char *s, *begin = beginIter.get();
+
+ for (s = begin; s != endIter.get(); ++s)
+ {
+ if (*s == '\\')
+ {
+ writing = true;
+ if (s > begin)
+ aResultBuf.Append(begin, s - begin);
+ aResultBuf += '/';
+ begin = s + 1;
+ }
+ }
+ if (writing && s > begin)
+ aResultBuf.Append(begin, s - begin);
+
+ return writing;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// miscellaneous (i.e., stuff that should really be elsewhere)
+//----------------------------------------------------------------------------
+
+static inline
+void ToLower(char &c)
+{
+ if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A'))
+ c += 'a' - 'A';
+}
+
+void
+net_ToLowerCase(char *str, uint32_t length)
+{
+ for (char *end = str + length; str < end; ++str)
+ ToLower(*str);
+}
+
+void
+net_ToLowerCase(char *str)
+{
+ for (; *str; ++str)
+ ToLower(*str);
+}
+
+char *
+net_FindCharInSet(const char *iter, const char *stop, const char *set)
+{
+ for (; iter != stop && *iter; ++iter) {
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s)
+ return (char *) iter;
+ }
+ }
+ return (char *) iter;
+}
+
+char *
+net_FindCharNotInSet(const char *iter, const char *stop, const char *set)
+{
+repeat:
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (++iter == stop)
+ break;
+ goto repeat;
+ }
+ }
+ return (char *) iter;
+}
+
+char *
+net_RFindCharNotInSet(const char *stop, const char *iter, const char *set)
+{
+ --iter;
+ --stop;
+
+ if (iter == stop)
+ return (char *) iter;
+
+repeat:
+ for (const char *s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (--iter == stop)
+ break;
+ goto repeat;
+ }
+ }
+ return (char *) iter;
+}
+
+#define HTTP_LWS " \t"
+
+// Return the index of the closing quote of the string, if any
+static uint32_t
+net_FindStringEnd(const nsCString& flatStr,
+ uint32_t stringStart,
+ char stringDelim)
+{
+ NS_ASSERTION(stringStart < flatStr.Length() &&
+ flatStr.CharAt(stringStart) == stringDelim &&
+ (stringDelim == '"' || stringDelim == '\''),
+ "Invalid stringStart");
+
+ const char set[] = { stringDelim, '\\', '\0' };
+ do {
+ // stringStart points to either the start quote or the last
+ // escaped char (the char following a '\\')
+
+ // Write to searchStart here, so that when we get back to the
+ // top of the loop right outside this one we search from the
+ // right place.
+ uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1);
+ if (stringEnd == uint32_t(kNotFound))
+ return flatStr.Length();
+
+ if (flatStr.CharAt(stringEnd) == '\\') {
+ // Hit a backslash-escaped char. Need to skip over it.
+ stringStart = stringEnd + 1;
+ if (stringStart == flatStr.Length())
+ return stringStart;
+
+ // Go back to looking for the next escape or the string end
+ continue;
+ }
+
+ return stringEnd;
+
+ } while (true);
+
+ NS_NOTREACHED("How did we get here?");
+ return flatStr.Length();
+}
+
+
+static uint32_t
+net_FindMediaDelimiter(const nsCString& flatStr,
+ uint32_t searchStart,
+ char delimiter)
+{
+ do {
+ // searchStart points to the spot from which we should start looking
+ // for the delimiter.
+ const char delimStr[] = { delimiter, '"', '\0' };
+ uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
+ if (curDelimPos == uint32_t(kNotFound))
+ return flatStr.Length();
+
+ char ch = flatStr.CharAt(curDelimPos);
+ if (ch == delimiter) {
+ // Found delimiter
+ return curDelimPos;
+ }
+
+ // We hit the start of a quoted string. Look for its end.
+ searchStart = net_FindStringEnd(flatStr, curDelimPos, ch);
+ if (searchStart == flatStr.Length())
+ return searchStart;
+
+ ++searchStart;
+
+ // searchStart now points to the first char after the end of the
+ // string, so just go back to the top of the loop and look for
+ // |delimiter| again.
+ } while (true);
+
+ NS_NOTREACHED("How did we get here?");
+ return flatStr.Length();
+}
+
+// aOffset should be added to aCharsetStart and aCharsetEnd if this
+// function sets them.
+static void
+net_ParseMediaType(const nsACString &aMediaTypeStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ int32_t aOffset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd,
+ bool aStrict)
+{
+ const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
+ const char* start = flatStr.get();
+ const char* end = start + flatStr.Length();
+
+ // Trim LWS leading and trailing whitespace from type. We include '(' in
+ // the trailing trim set to catch media-type comments, which are not at all
+ // standard, but may occur in rare cases.
+ const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
+ const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";(");
+
+ const char* charset = "";
+ const char* charsetEnd = charset;
+ int32_t charsetParamStart = 0;
+ int32_t charsetParamEnd = 0;
+
+ uint32_t consumed = typeEnd - type;
+
+ // Iterate over parameters
+ bool typeHasCharset = false;
+ uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
+ if (paramStart != uint32_t(kNotFound)) {
+ // We have parameters. Iterate over them.
+ uint32_t curParamStart = paramStart + 1;
+ do {
+ uint32_t curParamEnd =
+ net_FindMediaDelimiter(flatStr, curParamStart, ';');
+
+ const char* paramName = net_FindCharNotInSet(start + curParamStart,
+ start + curParamEnd,
+ HTTP_LWS);
+ static const char charsetStr[] = "charset=";
+ if (PL_strncasecmp(paramName, charsetStr,
+ sizeof(charsetStr) - 1) == 0) {
+ charset = paramName + sizeof(charsetStr) - 1;
+ charsetEnd = start + curParamEnd;
+ typeHasCharset = true;
+ charsetParamStart = curParamStart - 1;
+ charsetParamEnd = curParamEnd;
+ }
+
+ consumed = curParamEnd;
+ curParamStart = curParamEnd + 1;
+ } while (curParamStart < flatStr.Length());
+ }
+
+ bool charsetNeedsQuotedStringUnescaping = false;
+ if (typeHasCharset) {
+ // Trim LWS leading and trailing whitespace from charset. We include
+ // '(' in the trailing trim set to catch media-type comments, which are
+ // not at all standard, but may occur in rare cases.
+ charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
+ if (*charset == '"') {
+ charsetNeedsQuotedStringUnescaping = true;
+ charsetEnd =
+ start + net_FindStringEnd(flatStr, charset - start, *charset);
+ charset++;
+ NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
+ } else {
+ charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";(");
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type is the same as aContentType, then just update the
+ // charset. however, if charset is empty and aContentType hasn't
+ // changed, then don't wipe-out an existing aContentCharset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+
+ if (type != typeEnd &&
+ memchr(type, '/', typeEnd - type) != nullptr &&
+ (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end) :
+ (strncmp(type, "*/*", typeEnd - type) != 0))) {
+ // Common case here is that aContentType is empty
+ bool eq = !aContentType.IsEmpty() &&
+ aContentType.Equals(Substring(type, typeEnd),
+ nsCaseInsensitiveCStringComparator());
+ if (!eq) {
+ aContentType.Assign(type, typeEnd - type);
+ ToLowerCase(aContentType);
+ }
+
+ if ((!eq && *aHadCharset) || typeHasCharset) {
+ *aHadCharset = true;
+ if (charsetNeedsQuotedStringUnescaping) {
+ // parameters using the "quoted-string" syntax need
+ // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
+ aContentCharset.Truncate();
+ for (const char *c = charset; c != charsetEnd; c++) {
+ if (*c == '\\' && c + 1 != charsetEnd) {
+ // eat escape
+ c++;
+ }
+ aContentCharset.Append(*c);
+ }
+ }
+ else {
+ aContentCharset.Assign(charset, charsetEnd - charset);
+ }
+ if (typeHasCharset) {
+ *aCharsetStart = charsetParamStart + aOffset;
+ *aCharsetEnd = charsetParamEnd + aOffset;
+ }
+ }
+ // Only set a new charset position if this is a different type
+ // from the last one we had and it doesn't already have a
+ // charset param. If this is the same type, we probably want
+ // to leave the charset position on its first occurrence.
+ if (!eq && !typeHasCharset) {
+ int32_t charsetStart = int32_t(paramStart);
+ if (charsetStart == kNotFound)
+ charsetStart = flatStr.Length();
+
+ *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
+ }
+ }
+}
+
+#undef HTTP_LWS
+
+void
+net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset)
+{
+ int32_t dummy1, dummy2;
+ net_ParseContentType(aHeaderStr, aContentType, aContentCharset,
+ aHadCharset, &dummy1, &dummy2);
+}
+
+void
+net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd)
+{
+ //
+ // Augmented BNF (from RFC 2616 section 3.7):
+ //
+ // header-value = media-type *( LWS "," LWS media-type )
+ // media-type = type "/" subtype *( LWS ";" LWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = attribute "=" value
+ // attribute = token
+ // value = token | quoted-string
+ //
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html, text/html
+ // text/html,text/html; charset=ISO-8859-1
+ // text/html,text/html; charset="ISO-8859-1"
+ // text/html;charset=ISO-8859-1, text/html
+ // text/html;charset='ISO-8859-1', text/html
+ // application/octet-stream
+ //
+
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // iterate over media-types. Note that ',' characters can happen
+ // inside quoted strings, so we need to watch out for that.
+ uint32_t curTypeStart = 0;
+ do {
+ // curTypeStart points to the start of the current media-type. We want
+ // to look for its end.
+ uint32_t curTypeEnd =
+ net_FindMediaDelimiter(flatStr, curTypeStart, ',');
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ net_ParseMediaType(Substring(flatStr, curTypeStart,
+ curTypeEnd - curTypeStart),
+ aContentType, aContentCharset, curTypeStart,
+ aHadCharset, aCharsetStart, aCharsetEnd, false);
+
+ // And let's move on to the next media-type
+ curTypeStart = curTypeEnd + 1;
+ } while (curTypeStart < flatStr.Length());
+}
+
+void
+net_ParseRequestContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset)
+{
+ //
+ // Augmented BNF (from RFC 7231 section 3.1.1.1):
+ //
+ // media-type = type "/" subtype *( OWS ";" OWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = token "=" ( token / quoted-string )
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html; charset=ISO-8859-1
+ // text/html; charset="ISO-8859-1"
+ // application/octet-stream
+ //
+
+ aContentType.Truncate();
+ aContentCharset.Truncate();
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ nsAutoCString contentType, contentCharset;
+ bool hadCharset = false;
+ int32_t dummy1, dummy2;
+ uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+ if (typeEnd != flatStr.Length()) {
+ // We have some stuff left at the end, so this is not a valid
+ // request Content-Type header.
+ return;
+ }
+ net_ParseMediaType(flatStr, contentType, contentCharset, 0,
+ &hadCharset, &dummy1, &dummy2, true);
+
+ aContentType = contentType;
+ aContentCharset = contentCharset;
+ *aHadCharset = hadCharset;
+}
+
+bool
+net_IsValidHostName(const nsCSubstring &host)
+{
+ const char *end = host.EndReading();
+ // Use explicit whitelists to select which characters we are
+ // willing to send to lower-level DNS logic. This is more
+ // self-documenting, and can also be slightly faster than the
+ // blacklist approach, since DNS names are the common case, and
+ // the commonest characters will tend to be near the start of
+ // the list.
+
+ // Whitelist for DNS names (RFC 1035) with extra characters added
+ // for pragmatic reasons "$+_"
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
+ if (net_FindCharNotInSet(host.BeginReading(), end,
+ "abcdefghijklmnopqrstuvwxyz"
+ ".-0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end)
+ return true;
+
+ // Might be a valid IPv6 link-local address containing a percent sign
+ nsAutoCString strhost(host);
+ PRNetAddr addr;
+ return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
+}
+
+bool
+net_IsValidIPv4Addr(const char *addr, int32_t addrLen)
+{
+ RangedPtr<const char> p(addr, addrLen);
+
+ int32_t octet = -1; // means no digit yet
+ int32_t dotCount = 0; // number of dots in the address
+
+ for (; addrLen; ++p, --addrLen) {
+ if (*p == '.') {
+ dotCount++;
+ if (octet == -1) {
+ // invalid octet
+ return false;
+ }
+ octet = -1;
+ } else if (*p >= '0' && *p <='9') {
+ if (octet == 0) {
+ // leading 0 is not allowed
+ return false;
+ } else if (octet == -1) {
+ octet = *p - '0';
+ } else {
+ octet *= 10;
+ octet += *p - '0';
+ if (octet > 255)
+ return false;
+ }
+ } else {
+ // invalid character
+ return false;
+ }
+ }
+
+ return (dotCount == 3 && octet != -1);
+}
+
+bool
+net_IsValidIPv6Addr(const char *addr, int32_t addrLen)
+{
+ RangedPtr<const char> p(addr, addrLen);
+
+ int32_t digits = 0; // number of digits in current block
+ int32_t colons = 0; // number of colons in a row during parsing
+ int32_t blocks = 0; // number of hexadecimal blocks
+ bool haveZeros = false; // true if double colon is present in the address
+
+ for (; addrLen; ++p, --addrLen) {
+ if (*p == ':') {
+ if (colons == 0) {
+ if (digits != 0) {
+ digits = 0;
+ blocks++;
+ }
+ } else if (colons == 1) {
+ if (haveZeros)
+ return false; // only one occurrence is allowed
+ haveZeros = true;
+ } else {
+ // too many colons in a row
+ return false;
+ }
+ colons++;
+ } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') ||
+ (*p >= 'A' && *p <= 'F')) {
+ if (colons == 1 && blocks == 0) // starts with a single colon
+ return false;
+ if (digits == 4) // too many digits
+ return false;
+ colons = 0;
+ digits++;
+ } else if (*p == '.') {
+ // check valid IPv4 from the beginning of the last block
+ if (!net_IsValidIPv4Addr(p.get() - digits, addrLen + digits))
+ return false;
+ return (haveZeros && blocks < 6) || (!haveZeros && blocks == 6);
+ } else {
+ // invalid character
+ return false;
+ }
+ }
+
+ if (colons == 1) // ends with a single colon
+ return false;
+
+ if (digits) // there is a block at the end
+ blocks++;
+
+ return (haveZeros && blocks < 8) || (!haveZeros && blocks == 8);
+}
diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h
new file mode 100644
index 000000000..f30814c25
--- /dev/null
+++ b/netwerk/base/nsURLHelper.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLHelper_h__
+#define nsURLHelper_h__
+
+#include "nsString.h"
+
+class nsIFile;
+class nsIURLParser;
+
+enum netCoalesceFlags
+{
+ NET_COALESCE_NORMAL = 0,
+
+ /**
+ * retains /../ that reach above dir root (useful for FTP
+ * servers in which the root of the FTP URL is not necessarily
+ * the root of the FTP filesystem).
+ */
+ NET_COALESCE_ALLOW_RELATIVE_ROOT = 1<<0,
+
+ /**
+ * recognizes /%2F and // as markers for the root directory
+ * and handles them properly.
+ */
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT = 1<<1
+};
+
+//----------------------------------------------------------------------------
+// This module contains some private helper functions related to URL parsing.
+//----------------------------------------------------------------------------
+
+/* shutdown frees URL parser */
+void net_ShutdownURLHelper();
+#ifdef XP_MACOSX
+void net_ShutdownURLHelperOSX();
+#endif
+
+/* access URL parsers */
+nsIURLParser * net_GetAuthURLParser();
+nsIURLParser * net_GetNoAuthURLParser();
+nsIURLParser * net_GetStdURLParser();
+
+/* convert between nsIFile and file:// URL spec
+ * net_GetURLSpecFromFile does an extra stat, so callers should
+ * avoid it if possible in favor of net_GetURLSpecFromActualFile
+ * and net_GetURLSpecFromDir */
+nsresult net_GetURLSpecFromFile(nsIFile *, nsACString &);
+nsresult net_GetURLSpecFromDir(nsIFile *, nsACString &);
+nsresult net_GetURLSpecFromActualFile(nsIFile *, nsACString &);
+nsresult net_GetFileFromURLSpec(const nsACString &, nsIFile **);
+
+/* extract file path components from file:// URL */
+nsresult net_ParseFileURL(const nsACString &inURL,
+ nsACString &outDirectory,
+ nsACString &outFileBaseName,
+ nsACString &outFileExtension);
+
+/* handle .. in dirs while resolving URLs (path is UTF-8) */
+void net_CoalesceDirs(netCoalesceFlags flags, char* path);
+
+/**
+ * Resolves a relative path string containing "." and ".."
+ * with respect to a base path (assumed to already be resolved).
+ * For example, resolving "../../foo/./bar/../baz.html" w.r.t.
+ * "/a/b/c/d/e/" yields "/a/b/c/foo/baz.html". Attempting to
+ * ascend above the base results in the NS_ERROR_MALFORMED_URI
+ * exception. If basePath is null, it treats it as "/".
+ *
+ * @param relativePath a relative URI
+ * @param basePath a base URI
+ *
+ * @return a new string, representing canonical uri
+ */
+nsresult net_ResolveRelativePath(const nsACString &relativePath,
+ const nsACString &basePath,
+ nsACString &result);
+
+/**
+ * Check if a URL is absolute
+ *
+ * @param inURL URL spec
+ * @return true if the given spec represents an absolute URL
+ */
+bool net_IsAbsoluteURL(const nsACString& inURL);
+
+/**
+ * Extract URI-Scheme if possible
+ *
+ * @param inURI URI spec
+ * @param scheme scheme copied to this buffer on return (may be null)
+ */
+nsresult net_ExtractURLScheme(const nsACString &inURI,
+ nsACString &scheme);
+
+/* check that the given scheme conforms to RFC 2396 */
+bool net_IsValidScheme(const char *scheme, uint32_t schemeLen);
+
+inline bool net_IsValidScheme(const nsAFlatCString &scheme)
+{
+ return net_IsValidScheme(scheme.get(), scheme.Length());
+}
+
+/**
+ * This function strips out all C0 controls and space at the beginning and end
+ * of the URL and filters out \r, \n, \t from the middle of the URL. This makes
+ * it safe to call on things like javascript: urls or data: urls, where we may
+ * in fact run into whitespace that is not properly encoded.
+ *
+ * @param input the URL spec we want to filter
+ * @param result the out param to write to if filtering happens
+ */
+void net_FilterURIString(const nsACString& input, nsACString& result);
+
+#if defined(XP_WIN)
+/**
+ * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a
+ * forward-slash. This function maps any back-slashes to forward-slashes.
+ *
+ * @param aURL
+ * The URL string to normalize (UTF-8 encoded). This can be a
+ * relative URL segment.
+ * @param aResultBuf
+ * The resulting string is appended to this string. If the input URL
+ * is already normalized, then aResultBuf is unchanged.
+ *
+ * @returns false if aURL is already normalized. Otherwise, returns true.
+ */
+bool net_NormalizeFileURL(const nsACString &aURL,
+ nsCString &aResultBuf);
+#endif
+
+/*****************************************************************************
+ * generic string routines follow (XXX move to someplace more generic).
+ */
+
+/* convert to lower case */
+void net_ToLowerCase(char* str, uint32_t length);
+void net_ToLowerCase(char* str);
+
+/**
+ * returns pointer to first character of |str| in the given set. if not found,
+ * then |end| is returned. stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char * net_FindCharInSet(const char *str, const char *end, const char *set);
+
+/**
+ * returns pointer to first character of |str| NOT in the given set. if all
+ * characters are in the given set, then |end| is returned. if '\0' is not
+ * included in |set|, then stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char * net_FindCharNotInSet(const char *str, const char *end, const char *set);
+
+/**
+ * returns pointer to last character of |str| NOT in the given set. if all
+ * characters are in the given set, then |str - 1| is returned.
+ */
+char * net_RFindCharNotInSet(const char *str, const char *end, const char *set);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ *
+ * This parsing is suitable for HTTP request. Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool* aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ */
+void net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool* aHadCharset);
+/**
+ * As above, but also returns the start and end indexes for the charset
+ * parameter in aHeaderStr. These are indices for the entire parameter, NOT
+ * just the value. If there is "effectively" no charset parameter (e.g. if an
+ * earlier type with one is overridden by a later type without one),
+ * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that
+ * it's possible to have aContentCharset empty and *aHadCharset true when
+ * *aCharsetStart is nonnegative; this corresponds to charset="".
+ */
+void net_ParseContentType(const nsACString &aHeaderStr,
+ nsACString &aContentType,
+ nsACString &aContentCharset,
+ bool *aHadCharset,
+ int32_t *aCharsetStart,
+ int32_t *aCharsetEnd);
+
+/* inline versions */
+
+/* remember the 64-bit platforms ;-) */
+#define NET_MAX_ADDRESS (((char*)0)-1)
+
+inline char *net_FindCharInSet(const char *str, const char *set)
+{
+ return net_FindCharInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char *net_FindCharNotInSet(const char *str, const char *set)
+{
+ return net_FindCharNotInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char *net_RFindCharNotInSet(const char *str, const char *set)
+{
+ return net_RFindCharNotInSet(str, str + strlen(str), set);
+}
+
+/**
+ * This function returns true if the given hostname does not include any
+ * restricted characters. Otherwise, false is returned.
+ */
+bool net_IsValidHostName(const nsCSubstring &host);
+
+/**
+ * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv4Addr(const char *addr, int32_t addrLen);
+
+/**
+ * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv6Addr(const char *addr, int32_t addrLen);
+
+
+/**
+ * Returns the max length of a URL. The default is 1048576 (1 MB).
+ * Can be changed by pref "network.standard-url.max-length"
+ */
+int32_t net_GetURLMaxLength();
+
+#endif // !nsURLHelper_h__
diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp
new file mode 100644
index 000000000..bcc0b257f
--- /dev/null
+++ b/netwerk/base/nsURLHelperOSX.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Mac OS X-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include <Carbon/Carbon.h>
+
+static nsTArray<nsCString> *gVolumeList = nullptr;
+
+static bool pathBeginsWithVolName(const nsACString& path, nsACString& firstPathComponent)
+{
+ // Return whether the 1st path component in path (escaped) is equal to the name
+ // of a mounted volume. Return the 1st path component (unescaped) in any case.
+ // This needs to be done as quickly as possible, so we cache a list of volume names.
+ // XXX Register an event handler to detect drives being mounted/unmounted?
+
+ if (!gVolumeList) {
+ gVolumeList = new nsTArray<nsCString>;
+ if (!gVolumeList) {
+ return false; // out of memory
+ }
+ }
+
+ // Cache a list of volume names
+ if (!gVolumeList->Length()) {
+ OSErr err;
+ ItemCount volumeIndex = 1;
+
+ do {
+ HFSUniStr255 volName;
+ FSRef rootDirectory;
+ err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
+ &volName, &rootDirectory);
+ if (err == noErr) {
+ NS_ConvertUTF16toUTF8 volNameStr(Substring((char16_t *)volName.unicode,
+ (char16_t *)volName.unicode + volName.length));
+ gVolumeList->AppendElement(volNameStr);
+ volumeIndex++;
+ }
+ } while (err == noErr);
+ }
+
+ // Extract the first component of the path
+ nsACString::const_iterator start;
+ path.BeginReading(start);
+ start.advance(1); // path begins with '/'
+ nsACString::const_iterator directory_end;
+ path.EndReading(directory_end);
+ nsACString::const_iterator component_end(start);
+ FindCharInReadable('/', component_end, directory_end);
+
+ nsAutoCString flatComponent((Substring(start, component_end)));
+ NS_UnescapeURL(flatComponent);
+ int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
+ firstPathComponent = flatComponent;
+ return (foundIndex != -1);
+}
+
+void
+net_ShutdownURLHelperOSX()
+{
+ delete gVolumeList;
+ gVolumeList = nullptr;
+}
+
+static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath, nsACString& posixPath)
+{
+ // Use CFURL to do the conversion. We don't want to do this by simply
+ // using SwapSlashColon - we need the charset mapped from MacRoman
+ // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject to change)
+ // prepended if the path is not on the boot drive.
+
+ CFStringRef pathStrRef = CFStringCreateWithCString(nullptr,
+ PromiseFlatCString(hfsPath).get(),
+ kCFStringEncodingMacRoman);
+ if (!pathStrRef)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr,
+ pathStrRef, kCFURLHFSPathStyle, true);
+ if (urlRef) {
+ UInt8 pathBuf[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf, sizeof(pathBuf))) {
+ posixPath = (char *)pathBuf;
+ rv = NS_OK;
+ }
+ }
+ CFRelease(pathStrRef);
+ if (urlRef)
+ CFRelease(urlRef);
+ return rv;
+}
+
+static void SwapSlashColon(char *s)
+{
+ while (*s) {
+ if (*s == '/')
+ *s = ':';
+ else if (*s == ':')
+ *s = '/';
+ s++;
+ }
+}
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
+
+ nsresult rv;
+ nsAutoCString ePath;
+
+ // construct URL spec from native file path
+ rv = aFile->GetNativePath(ePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString escPath;
+ NS_NAMED_LITERAL_CSTRING(prefix, "file://");
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ // NOTE: See also the implementation in nsURLHelperUnix.cpp
+ // This matches it except for the HFS path handling.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+ bool bHFSPath = false;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path);
+
+ // The canonical form of file URLs on OSX use POSIX paths:
+ // file:///path-name.
+ // But, we still encounter file URLs that use HFS paths:
+ // file:///volume-name/path-name
+ // Determine that here and normalize HFS paths to POSIX.
+ nsAutoCString possibleVolName;
+ if (pathBeginsWithVolName(directory, possibleVolName)) {
+ // Though we know it begins with a volume name, it could still
+ // be a valid POSIX path if the boot drive is named "Mac HD"
+ // and there is a directory "Mac HD" at its root. If such a
+ // directory doesn't exist, we'll assume this is an HFS path.
+ FSRef testRef;
+ possibleVolName.Insert("/", 0);
+ if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) != noErr)
+ bHFSPath = true;
+ }
+
+ if (bHFSPath) {
+ // "%2F"s need to become slashes, while all other slashes need to
+ // become colons. If we start out by changing "%2F"s to colons, we
+ // can reply on SwapSlashColon() to do what we need
+ path.ReplaceSubstring("%2F", ":");
+ path.Cut(0, 1); // directory begins with '/'
+ SwapSlashColon((char *)path.get());
+ // At this point, path is an HFS path made using the same
+ // algorithm as nsURLHelperMac. We'll convert to POSIX below.
+ }
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ if (bHFSPath)
+ convertHFSPathtoPOSIX(path, path);
+
+ // assuming path is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperUnix.cpp b/netwerk/base/nsURLHelperUnix.cpp
new file mode 100644
index 000000000..5b36770d7
--- /dev/null
+++ b/netwerk/base/nsURLHelperUnix.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Unix-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ nsresult rv;
+ nsAutoCString nativePath, ePath;
+ nsAutoString path;
+
+ rv = aFile->GetNativePath(nativePath);
+ if (NS_FAILED(rv)) return rv;
+
+ // Convert to unicode and back to check correct conversion to native charset
+ NS_CopyNativeToUnicode(nativePath, path);
+ NS_CopyUnicodeToNative(path, ePath);
+
+ // Use UTF8 version if conversion was successful
+ if (nativePath == ePath)
+ CopyUTF16toUTF8(path, ePath);
+ else
+ ePath = nativePath;
+
+ nsAutoCString escPath;
+ NS_NAMED_LITERAL_CSTRING(prefix, "file://");
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ // NOTE: See also the implementation in nsURLHelperOSX.cpp,
+ // which is based on this.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ rv = NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ if (!fileBaseName.IsEmpty()) {
+ rv = NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ rv = NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ if (IsUTF8(path)) {
+ // speed up the start-up where UTF-8 is the native charset
+ // (e.g. on recent Linux distributions)
+ if (NS_IsNativeUTF8())
+ rv = localFile->InitWithNativePath(path);
+ else
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ }
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperWin.cpp b/netwerk/base/nsURLHelperWin.cpp
new file mode 100644
index 000000000..4ec46db4e
--- /dev/null
+++ b/netwerk/base/nsURLHelperWin.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Windows-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include <windows.h>
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ nsresult rv;
+ nsAutoString path;
+
+ // construct URL spec from file path
+ rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Replace \ with / to convert to an url
+ path.ReplaceChar(char16_t(0x5Cu), char16_t(0x2Fu));
+
+ nsAutoCString escPath;
+
+ // Windows Desktop paths begin with a drive letter, so need an 'extra'
+ // slash at the begining
+ // C:\Windows => file:///C:/Windows
+ NS_NAMED_LITERAL_CSTRING(prefix, "file:///");
+
+ // Escape the path with the directory mask
+ NS_ConvertUTF16toUTF8 ePath(path);
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ nsresult rv;
+
+ if (aURL.Length() > (uint32_t) net_GetURLMaxLength()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIFile> localFile(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Only nsIFile supported right now");
+ return rv;
+ }
+
+ localFile->SetFollowLinks(true);
+
+ const nsACString *specPtr;
+
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(aURL, buf))
+ specPtr = &buf;
+ else
+ specPtr = &aURL;
+
+ nsAutoCString directory, fileBaseName, fileExtension;
+
+ rv = net_ParseFileURL(*specPtr, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path);
+ if (path.Length() > 2 && path.CharAt(2) == '|')
+ path.SetCharAt(':', 2);
+ path.ReplaceChar('/', '\\');
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ // remove leading '\'
+ if (path.CharAt(0) == '\\')
+ path.Cut(0, 1);
+
+ if (IsUTF8(path))
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLParsers.cpp b/netwerk/base/nsURLParsers.cpp
new file mode 100644
index 000000000..b75ee0c4d
--- /dev/null
+++ b/netwerk/base/nsURLParsers.cpp
@@ -0,0 +1,702 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "mozilla/RangedPtr.h"
+
+#include "nsURLParsers.h"
+#include "nsURLHelper.h"
+#include "nsString.h"
+#include "nsCRT.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+
+static uint32_t
+CountConsecutiveSlashes(const char *str, int32_t len)
+{
+ RangedPtr<const char> p(str, len);
+ uint32_t count = 0;
+ while (len-- && *p++ == '/') ++count;
+ return count;
+}
+
+//----------------------------------------------------------------------------
+// nsBaseURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsAuthURLParser, nsIURLParser)
+NS_IMPL_ISUPPORTS(nsNoAuthURLParser, nsIURLParser)
+
+#define SET_RESULT(component, pos, len) \
+ PR_BEGIN_MACRO \
+ if (component ## Pos) \
+ *component ## Pos = uint32_t(pos); \
+ if (component ## Len) \
+ *component ## Len = int32_t(len); \
+ PR_END_MACRO
+
+#define OFFSET_RESULT(component, offset) \
+ PR_BEGIN_MACRO \
+ if (component ## Pos) \
+ *component ## Pos += offset; \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseURL(const char *spec, int32_t specLen,
+ uint32_t *schemePos, int32_t *schemeLen,
+ uint32_t *authorityPos, int32_t *authorityLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ if (NS_WARN_IF(!spec)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (specLen < 0)
+ specLen = strlen(spec);
+
+ const char *stop = nullptr;
+ const char *colon = nullptr;
+ const char *slash = nullptr;
+ const char *p = spec;
+ uint32_t offset = 0;
+ int32_t len = specLen;
+
+ // skip leading whitespace
+ while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') {
+ spec++;
+ specLen--;
+ offset++;
+
+ p++;
+ len--;
+ }
+
+ for (; len && *p && !colon && !slash; ++p, --len) {
+ switch (*p) {
+ case ':':
+ if (!colon)
+ colon = p;
+ break;
+ case '/': // start of filepath
+ case '?': // start of query
+ case '#': // start of ref
+ if (!slash)
+ slash = p;
+ break;
+ case '@': // username@hostname
+ case '[': // start of IPv6 address literal
+ if (!stop)
+ stop = p;
+ break;
+ }
+ }
+ // disregard the first colon if it follows an '@' or a '['
+ if (colon && stop && colon > stop)
+ colon = nullptr;
+
+ // if the spec only contained whitespace ...
+ if (specLen == 0) {
+ SET_RESULT(scheme, 0, -1);
+ SET_RESULT(authority, 0, 0);
+ SET_RESULT(path, 0, 0);
+ return NS_OK;
+ }
+
+ // ignore trailing whitespace and control characters
+ for (p = spec + specLen - 1; ((unsigned char) *p <= ' ') && (p != spec); --p)
+ ;
+
+ specLen = p - spec + 1;
+
+ if (colon && (colon < slash || !slash)) {
+ //
+ // spec = <scheme>:/<the-rest>
+ //
+ // or
+ //
+ // spec = <scheme>:<authority>
+ // spec = <scheme>:<path-no-slashes>
+ //
+ if (!net_IsValidScheme(spec, colon - spec) || (*(colon+1) == ':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(scheme, offset, colon - spec);
+ if (authorityLen || pathLen) {
+ uint32_t schemeLen = colon + 1 - spec;
+ offset += schemeLen;
+ ParseAfterScheme(colon + 1, specLen - schemeLen,
+ authorityPos, authorityLen,
+ pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ else {
+ //
+ // spec = <authority-no-port-or-password>/<path>
+ // spec = <path>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>/<path-with-colon>
+ // spec = <path-with-colon>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>
+ // spec = <path-no-slashes-or-colon>
+ //
+ SET_RESULT(scheme, 0, -1);
+ if (authorityLen || pathLen) {
+ ParseAfterScheme(spec, specLen,
+ authorityPos, authorityLen,
+ pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0)
+ authLen = strlen(auth);
+
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, authLen);
+ if (port)
+ *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen)
+{
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ SET_RESULT(hostname, 0, -1);
+ if (port)
+ *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParsePath(const char *path, int32_t pathLen,
+ uint32_t *filepathPos, int32_t *filepathLen,
+ uint32_t *queryPos, int32_t *queryLen,
+ uint32_t *refPos, int32_t *refLen)
+{
+ if (NS_WARN_IF(!path)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (pathLen < 0)
+ pathLen = strlen(path);
+
+ // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref>
+
+ // XXX PL_strnpbrk would be nice, but it's buggy
+
+ // search for first occurrence of either ? or #
+ const char *query_beg = 0, *query_end = 0;
+ const char *ref_beg = 0;
+ const char *p = 0;
+ for (p = path; p < path + pathLen; ++p) {
+ // only match the query string if it precedes the reference fragment
+ if (!ref_beg && !query_beg && *p == '?')
+ query_beg = p + 1;
+ else if (*p == '#') {
+ ref_beg = p + 1;
+ if (query_beg)
+ query_end = p;
+ break;
+ }
+ }
+
+ if (query_beg) {
+ if (query_end)
+ SET_RESULT(query, query_beg - path, query_end - query_beg);
+ else
+ SET_RESULT(query, query_beg - path, pathLen - (query_beg - path));
+ }
+ else
+ SET_RESULT(query, 0, -1);
+
+ if (ref_beg)
+ SET_RESULT(ref, ref_beg - path, pathLen - (ref_beg - path));
+ else
+ SET_RESULT(ref, 0, -1);
+
+ const char *end;
+ if (query_beg)
+ end = query_beg - 1;
+ else if (ref_beg)
+ end = ref_beg - 1;
+ else
+ end = path + pathLen;
+
+ // an empty file path is no file path
+ if (end != path)
+ SET_RESULT(filepath, 0, end - path);
+ else
+ SET_RESULT(filepath, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseFilePath(const char *filepath, int32_t filepathLen,
+ uint32_t *directoryPos, int32_t *directoryLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0)
+ filepathLen = strlen(filepath);
+
+ if (filepathLen == 0) {
+ SET_RESULT(directory, 0, -1);
+ SET_RESULT(basename, 0, 0); // assume a zero length file basename
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+
+ const char *p;
+ const char *end = filepath + filepathLen;
+
+ // search backwards for filename
+ for (p = end - 1; *p != '/' && p > filepath; --p)
+ ;
+ if (*p == '/') {
+ // catch /.. and /.
+ if ((p+1 < end && *(p+1) == '.') &&
+ (p+2 == end || (*(p+2) == '.' && p+3 == end)))
+ p = end - 1;
+ // filepath = <directory><filename>.<extension>
+ SET_RESULT(directory, 0, p - filepath + 1);
+ ParseFileName(p + 1, end - (p + 1),
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+ OFFSET_RESULT(basename, p + 1 - filepath);
+ OFFSET_RESULT(extension, p + 1 - filepath);
+ }
+ else {
+ // filepath = <filename>.<extension>
+ SET_RESULT(directory, 0, -1);
+ ParseFileName(filepath, filepathLen,
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsBaseURLParser::ParseFileName(const char *filename, int32_t filenameLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filename)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filenameLen < 0)
+ filenameLen = strlen(filename);
+
+ // no extension if filename ends with a '.'
+ if (filename[filenameLen-1] != '.') {
+ // ignore '.' at the beginning
+ for (const char *p = filename + filenameLen - 1; p > filename; --p) {
+ if (*p == '.') {
+ // filename = <basename.extension>
+ SET_RESULT(basename, 0, p - filename);
+ SET_RESULT(extension, p + 1 - filename, filenameLen - (p - filename + 1));
+ return NS_OK;
+ }
+ }
+ }
+ // filename = <basename>
+ SET_RESULT(basename, 0, filenameLen);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsNoAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ NS_NOTREACHED("Shouldn't parse auth in a NoAuthURL!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void
+nsNoAuthURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ // everything is the path
+ uint32_t pos = 0;
+ switch (CountConsecutiveSlashes(spec, specLen)) {
+ case 0:
+ case 1:
+ break;
+ case 2:
+ {
+ const char *p = nullptr;
+ if (specLen > 2) {
+ // looks like there is an authority section
+#if defined(XP_WIN)
+ // if the authority looks like a drive number then we
+ // really want to treat it as part of the path
+ // [a-zA-Z][:|]{/\}
+ // i.e one of: c: c:\foo c:/foo c| c|\foo c|/foo
+ if ((specLen > 3) && (spec[3] == ':' || spec[3] == '|') &&
+ nsCRT::IsAsciiAlpha(spec[2]) &&
+ ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) {
+ pos = 1;
+ break;
+ }
+#endif
+ // Ignore apparent authority; path is everything after it
+ for (p = spec + 2; p < spec + specLen; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#')
+ break;
+ }
+ }
+ SET_RESULT(auth, 0, -1);
+ if (p && p != spec+specLen)
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ else
+ SET_RESULT(path, 0, -1);
+ return;
+ }
+ default:
+ pos = 2;
+ break;
+ }
+ SET_RESULT(auth, pos, 0);
+ SET_RESULT(path, pos, specLen - pos);
+}
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseFilePath(const char *filepath, int32_t filepathLen,
+ uint32_t *directoryPos, int32_t *directoryLen,
+ uint32_t *basenamePos, int32_t *basenameLen,
+ uint32_t *extensionPos, int32_t *extensionLen)
+{
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0)
+ filepathLen = strlen(filepath);
+
+ // look for a filepath consisting of only a drive number, which may or
+ // may not have a leading slash.
+ if (filepathLen > 1 && filepathLen < 4) {
+ const char *end = filepath + filepathLen;
+ const char *p = filepath;
+ if (*p == '/')
+ p++;
+ if ((end-p == 2) && (p[1]==':' || p[1]=='|') && nsCRT::IsAsciiAlpha(*p)) {
+ // filepath = <drive-number>:
+ SET_RESULT(directory, 0, filepathLen);
+ SET_RESULT(basename, 0, -1);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+ }
+
+ // otherwise fallback on common implementation
+ return nsBaseURLParser::ParseFilePath(filepath, filepathLen,
+ directoryPos, directoryLen,
+ basenamePos, basenameLen,
+ extensionPos, extensionLen);
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ nsresult rv;
+
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0)
+ authLen = strlen(auth);
+
+ if (authLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, 0);
+ if (port)
+ *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for @
+ const char *p = auth + authLen - 1;
+ for (; (*p != '@') && (p > auth); --p) {
+ continue;
+ }
+ if ( *p == '@' ) {
+ // auth = <user-info@server-info>
+ rv = ParseUserInfo(auth, p - auth,
+ usernamePos, usernameLen,
+ passwordPos, passwordLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = ParseServerInfo(p + 1, authLen - (p - auth + 1),
+ hostnamePos, hostnameLen,
+ port);
+ if (NS_FAILED(rv)) return rv;
+ OFFSET_RESULT(hostname, p + 1 - auth);
+
+ // malformed if has a username or password
+ // but no host info, such as: http://u:p@/
+ if ((usernamePos || passwordPos) && (!hostnamePos || !*hostnameLen)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ else {
+ // auth = <server-info>
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ rv = ParseServerInfo(auth, authLen,
+ hostnamePos, hostnameLen,
+ port);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen)
+{
+ if (NS_WARN_IF(!userinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (userinfoLen < 0)
+ userinfoLen = strlen(userinfo);
+
+ if (userinfoLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+ }
+
+ const char *p = (const char *) memchr(userinfo, ':', userinfoLen);
+ if (p) {
+ // userinfo = <username:password>
+ if (p == userinfo) {
+ // must have a username!
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(username, 0, p - userinfo);
+ SET_RESULT(password, p - userinfo + 1, userinfoLen - (p - userinfo + 1));
+ }
+ else {
+ // userinfo = <username>
+ SET_RESULT(username, 0, userinfoLen);
+ SET_RESULT(password, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port)
+{
+ if (NS_WARN_IF(!serverinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (serverinfoLen < 0)
+ serverinfoLen = strlen(serverinfo);
+
+ if (serverinfoLen == 0) {
+ SET_RESULT(hostname, 0, 0);
+ if (port)
+ *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for a ':' but stop on ']' (IPv6 address literal
+ // delimiter). check for illegal characters in the hostname.
+ const char *p = serverinfo + serverinfoLen - 1;
+ const char *colon = nullptr, *bracket = nullptr;
+ for (; p > serverinfo; --p) {
+ switch (*p) {
+ case ']':
+ bracket = p;
+ break;
+ case ':':
+ if (bracket == nullptr)
+ colon = p;
+ break;
+ case ' ':
+ // hostname must not contain a space
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ if (colon) {
+ // serverinfo = <hostname:port>
+ SET_RESULT(hostname, 0, colon - serverinfo);
+ if (port) {
+ // XXX unfortunately ToInteger is not defined for substrings
+ nsAutoCString buf(colon+1, serverinfoLen - (colon + 1 - serverinfo));
+ if (buf.Length() == 0) {
+ *port = -1;
+ }
+ else {
+ const char* nondigit = NS_strspnp("0123456789", buf.get());
+ if (nondigit && *nondigit)
+ return NS_ERROR_MALFORMED_URI;
+
+ nsresult err;
+ *port = buf.ToInteger(&err);
+ if (NS_FAILED(err) || *port < 0 || *port > std::numeric_limits<uint16_t>::max())
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+ else {
+ // serverinfo = <hostname>
+ SET_RESULT(hostname, 0, serverinfoLen);
+ if (port)
+ *port = -1;
+ }
+
+ // In case of IPv6 address check its validity
+ if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' &&
+ *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' &&
+ !net_IsValidIPv6Addr(serverinfo + *hostnamePos + 1, *hostnameLen - 2))
+ return NS_ERROR_MALFORMED_URI;
+
+ return NS_OK;
+}
+
+void
+nsAuthURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char *end = spec + specLen;
+ const char *p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#')
+ break;
+ }
+ if (p < end) {
+ // spec = [/]<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ }
+ else {
+ // spec = [/]<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsStdURLParser implementation
+//----------------------------------------------------------------------------
+
+void
+nsStdURLParser::ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen)
+{
+ NS_PRECONDITION(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char *end = spec + specLen;
+ const char *p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (strchr("/?#;", *p))
+ break;
+ }
+ switch (nslash) {
+ case 0:
+ case 2:
+ if (p < end) {
+ // spec = (//)<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ }
+ else {
+ // spec = (//)<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+ break;
+ case 1:
+ // spec = /<path>
+ SET_RESULT(auth, 0, -1);
+ SET_RESULT(path, 0, specLen);
+ break;
+ default:
+ // spec = ///[/]<path>
+ SET_RESULT(auth, 2, 0);
+ SET_RESULT(path, 2, specLen - 2);
+ }
+}
diff --git a/netwerk/base/nsURLParsers.h b/netwerk/base/nsURLParsers.h
new file mode 100644
index 000000000..34de99a37
--- /dev/null
+++ b/netwerk/base/nsURLParsers.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLParsers_h__
+#define nsURLParsers_h__
+
+#include "nsIURLParser.h"
+#include "mozilla/Attributes.h"
+
+//----------------------------------------------------------------------------
+// base class for url parsers
+//----------------------------------------------------------------------------
+
+class nsBaseURLParser : public nsIURLParser
+{
+public:
+ NS_DECL_NSIURLPARSER
+
+ nsBaseURLParser() { }
+
+protected:
+ // implemented by subclasses
+ virtual void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) = 0;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that do not have an authority section
+//
+// eg. file:foo/bar.txt
+// file:/foo/bar.txt (treated equivalently)
+// file:///foo/bar.txt
+//
+// eg. file:////foo/bar.txt (UNC-filepath = \\foo\bar.txt)
+//
+// XXX except in this case:
+// file://foo/bar.txt (the authority "foo" is ignored)
+//----------------------------------------------------------------------------
+
+class nsNoAuthURLParser final : public nsBaseURLParser
+{
+ ~nsNoAuthURLParser() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+#if defined(XP_WIN)
+ NS_IMETHOD ParseFilePath(const char *, int32_t,
+ uint32_t *, int32_t *,
+ uint32_t *, int32_t *,
+ uint32_t *, int32_t *) override;
+#endif
+
+ NS_IMETHOD ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that must have an authority section
+//
+// eg. http:www.foo.com/bar.html
+// http:/www.foo.com/bar.html
+// http://www.foo.com/bar.html (treated equivalently)
+// http:///www.foo.com/bar.html
+//----------------------------------------------------------------------------
+
+class nsAuthURLParser : public nsBaseURLParser
+{
+protected:
+ virtual ~nsAuthURLParser() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD ParseAuthority(const char *auth, int32_t authLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ NS_IMETHOD ParseUserInfo(const char *userinfo, int32_t userinfoLen,
+ uint32_t *usernamePos, int32_t *usernameLen,
+ uint32_t *passwordPos, int32_t *passwordLen) override;
+
+ NS_IMETHOD ParseServerInfo(const char *serverinfo, int32_t serverinfoLen,
+ uint32_t *hostnamePos, int32_t *hostnameLen,
+ int32_t *port) override;
+
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that may or may not have an authority section
+//
+// eg. http:www.foo.com (www.foo.com is authority)
+// http:www.foo.com/bar.html (www.foo.com is authority)
+// http:/www.foo.com/bar.html (www.foo.com is part of file path)
+// http://www.foo.com/bar.html (www.foo.com is authority)
+// http:///www.foo.com/bar.html (www.foo.com is part of file path)
+//----------------------------------------------------------------------------
+
+class nsStdURLParser : public nsAuthURLParser
+{
+ virtual ~nsStdURLParser() {}
+
+public:
+ void ParseAfterScheme(const char *spec, int32_t specLen,
+ uint32_t *authPos, int32_t *authLen,
+ uint32_t *pathPos, int32_t *pathLen);
+};
+
+#endif // nsURLParsers_h__
diff --git a/netwerk/base/nsUnicharStreamLoader.cpp b/netwerk/base/nsUnicharStreamLoader.cpp
new file mode 100644
index 000000000..115acf9ae
--- /dev/null
+++ b/netwerk/base/nsUnicharStreamLoader.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsUnicharStreamLoader.h"
+#include "nsIInputStream.h"
+#include <algorithm>
+#include "mozilla/dom/EncodingUtils.h"
+
+// 1024 bytes is specified in
+// http://www.whatwg.org/specs/web-apps/current-work/#charset for HTML; for
+// other resource types (e.g. CSS) typically fewer bytes are fine too, since
+// they only look at things right at the beginning of the data.
+#define SNIFFING_BUFFER_SIZE 1024
+
+using namespace mozilla;
+using mozilla::dom::EncodingUtils;
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::Init(nsIUnicharStreamLoaderObserver *aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ mObserver = aObserver;
+
+ if (!mRawData.SetCapacity(SNIFFING_BUFFER_SIZE, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsresult
+nsUnicharStreamLoader::Create(nsISupports *aOuter,
+ REFNSIID aIID,
+ void **aResult)
+{
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ nsUnicharStreamLoader* it = new nsUnicharStreamLoader();
+ NS_ADDREF(it);
+ nsresult rv = it->QueryInterface(aIID, aResult);
+ NS_RELEASE(it);
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsUnicharStreamLoader, nsIUnicharStreamLoader,
+ nsIRequestObserver, nsIStreamListener)
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetChannel(nsIChannel **aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetCharset(nsACString& aCharset)
+{
+ aCharset = mCharset;
+ return NS_OK;
+}
+
+/* nsIRequestObserver implementation */
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnStartRequest(nsIRequest*, nsISupports*)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ if (!mObserver) {
+ NS_ERROR("nsUnicharStreamLoader::OnStopRequest called before ::Init");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mContext = aContext;
+ mChannel = do_QueryInterface(aRequest);
+
+ nsresult rv = NS_OK;
+ if (mRawData.Length() > 0 && NS_SUCCEEDED(aStatus)) {
+ MOZ_ASSERT(mBuffer.Length() == 0,
+ "should not have both decoded and raw data");
+ rv = DetermineCharset();
+ }
+
+ if (NS_FAILED(rv)) {
+ // Call the observer but pass it no data.
+ mObserver->OnStreamComplete(this, mContext, rv, EmptyString());
+ } else {
+ mObserver->OnStreamComplete(this, mContext, aStatus, mBuffer);
+ }
+
+ mObserver = nullptr;
+ mDecoder = nullptr;
+ mContext = nullptr;
+ mChannel = nullptr;
+ mCharset.Truncate();
+ mRawData.Truncate();
+ mRawBuffer.Truncate();
+ mBuffer.Truncate();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetRawBuffer(nsACString& aRawBuffer)
+{
+ aRawBuffer = mRawBuffer;
+ return NS_OK;
+}
+
+/* nsIStreamListener implementation */
+NS_IMETHODIMP
+nsUnicharStreamLoader::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ if (!mObserver) {
+ NS_ERROR("nsUnicharStreamLoader::OnDataAvailable called before ::Init");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mContext = aContext;
+ mChannel = do_QueryInterface(aRequest);
+
+ nsresult rv = NS_OK;
+ if (mDecoder) {
+ // process everything we've got
+ uint32_t dummy;
+ aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
+ } else {
+ // no decoder yet. Read up to SNIFFING_BUFFER_SIZE octets into
+ // mRawData (this is the cutoff specified in
+ // draft-abarth-mime-sniff-06). If we can get that much, then go
+ // ahead and fire charset detection and read the rest. Otherwise
+ // wait for more data.
+
+ uint32_t haveRead = mRawData.Length();
+ uint32_t toRead = std::min(SNIFFING_BUFFER_SIZE - haveRead, aCount);
+ uint32_t n;
+ char *here = mRawData.BeginWriting() + haveRead;
+
+ rv = aInputStream->Read(here, toRead, &n);
+ if (NS_SUCCEEDED(rv)) {
+ mRawData.SetLength(haveRead + n);
+ if (mRawData.Length() == SNIFFING_BUFFER_SIZE) {
+ rv = DetermineCharset();
+ if (NS_SUCCEEDED(rv)) {
+ // process what's left
+ uint32_t dummy;
+ aInputStream->ReadSegments(WriteSegmentFun, this, aCount - n, &dummy);
+ }
+ } else {
+ MOZ_ASSERT(n == aCount, "didn't read as much as was available");
+ }
+ }
+ }
+
+ mContext = nullptr;
+ mChannel = nullptr;
+ return rv;
+}
+
+nsresult
+nsUnicharStreamLoader::DetermineCharset()
+{
+ nsresult rv = mObserver->OnDetermineCharset(this, mContext,
+ mRawData, mCharset);
+ if (NS_FAILED(rv) || mCharset.IsEmpty()) {
+ // The observer told us nothing useful
+ mCharset.AssignLiteral("UTF-8");
+ }
+
+ // Sadly, nsIUnicharStreamLoader is exposed to extensions, so we can't
+ // assume mozilla::css::Loader to be the only caller. Special-casing
+ // replacement, since it's not invariant under a second label resolution
+ // operation.
+ if (mCharset.EqualsLiteral("replacement")) {
+ mDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ } else {
+ nsAutoCString charset;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(mCharset, charset)) {
+ // If we got replacement here, the caller was not mozilla::css::Loader
+ // but an extension.
+ return NS_ERROR_UCONV_NOCONV;
+ }
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ // Process the data into mBuffer
+ uint32_t dummy;
+ rv = WriteSegmentFun(nullptr, this,
+ mRawData.BeginReading(),
+ 0, mRawData.Length(),
+ &dummy);
+ mRawData.Truncate();
+ return rv;
+}
+
+nsresult
+nsUnicharStreamLoader::WriteSegmentFun(nsIInputStream *,
+ void *aClosure,
+ const char *aSegment,
+ uint32_t,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ nsUnicharStreamLoader* self = static_cast<nsUnicharStreamLoader*>(aClosure);
+
+ uint32_t haveRead = self->mBuffer.Length();
+ int32_t srcLen = aCount;
+ int32_t dstLen;
+
+ nsresult rv = self->mDecoder->GetMaxLength(aSegment, srcLen, &dstLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t capacity = haveRead + dstLen;
+ if (!self->mBuffer.SetCapacity(capacity, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!self->mRawBuffer.Append(aSegment, aCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = self->mDecoder->Convert(aSegment,
+ &srcLen,
+ self->mBuffer.BeginWriting() + haveRead,
+ &dstLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(srcLen == static_cast<int32_t>(aCount));
+ haveRead += dstLen;
+
+ self->mBuffer.SetLength(haveRead);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsUnicharStreamLoader.h b/netwerk/base/nsUnicharStreamLoader.h
new file mode 100644
index 000000000..298fb9e11
--- /dev/null
+++ b/netwerk/base/nsUnicharStreamLoader.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUnicharStreamLoader_h__
+#define nsUnicharStreamLoader_h__
+
+#include "nsIChannel.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIInputStream;
+
+class nsUnicharStreamLoader : public nsIUnicharStreamLoader
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUNICHARSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsUnicharStreamLoader() {}
+
+ static nsresult Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~nsUnicharStreamLoader() {}
+
+ nsresult DetermineCharset();
+
+ /**
+ * callback method used for ReadSegments
+ */
+ static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *,
+ uint32_t, uint32_t, uint32_t *);
+
+ nsCOMPtr<nsIUnicharStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+ nsCOMPtr<nsISupports> mContext;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCString mCharset;
+
+ // This holds the first up-to-512 bytes of the raw stream.
+ // It will be passed to the OnDetermineCharset callback.
+ nsCString mRawData;
+
+ // Holds complete raw bytes as received so that SRI checks can be
+ // calculated on the raw data prior to character conversion.
+ nsCString mRawBuffer;
+
+ // This holds the complete contents of the stream so far, after
+ // decoding to UTF-16. It will be passed to the OnStreamComplete
+ // callback.
+ nsString mBuffer;
+};
+
+#endif // nsUnicharStreamLoader_h__
diff --git a/netwerk/base/rust-url-capi/.gitignore b/netwerk/base/rust-url-capi/.gitignore
new file mode 100644
index 000000000..4fffb2f89
--- /dev/null
+++ b/netwerk/base/rust-url-capi/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/netwerk/base/rust-url-capi/Cargo.toml b/netwerk/base/rust-url-capi/Cargo.toml
new file mode 100644
index 000000000..ecdb53058
--- /dev/null
+++ b/netwerk/base/rust-url-capi/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+
+name = "rust_url_capi"
+version = "0.0.1"
+authors = ["Valentin Gosu <valentin.gosu@gmail.com>"]
+
+[profile.dev]
+opt-level = 3
+debug = true
+rpath = true
+lto = true
+
+[lib]
+name = "rust_url_capi"
+
+
+[dependencies]
+libc = "0.2.0"
+url = "1.2.1"
diff --git a/netwerk/base/rust-url-capi/src/error_mapping.rs b/netwerk/base/rust-url-capi/src/error_mapping.rs
new file mode 100644
index 000000000..f20afb263
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/error_mapping.rs
@@ -0,0 +1,68 @@
+use url::ParseError;
+
+pub trait ErrorCode {
+ fn error_code(&self) -> i32;
+}
+
+impl<T: ErrorCode> ErrorCode for Result<(), T> {
+ fn error_code(&self) -> i32 {
+ match *self {
+ Ok(_) => 0,
+ Err(ref error) => error.error_code(),
+ }
+ }
+}
+
+impl ErrorCode for () {
+ fn error_code(&self) -> i32 {
+ return -1;
+ }
+}
+impl ErrorCode for ParseError {
+ fn error_code(&self) -> i32 {
+ return -1;
+// match *self {
+// ParseError::EmptyHost => -1,
+// ParseError::InvalidScheme => -2,
+// ParseError::InvalidPort => -3,
+// ParseError::InvalidIpv6Address => -4,
+// ParseError::InvalidDomainCharacter => -5,
+// ParseError::InvalidCharacter => -6,
+// ParseError::InvalidBackslash => -7,
+// ParseError::InvalidPercentEncoded => -8,
+// ParseError::InvalidAtSymbolInUser => -9,
+// ParseError::ExpectedTwoSlashes => -10,
+// ParseError::ExpectedInitialSlash => -11,
+// ParseError::NonUrlCodePoint => -12,
+// ParseError::RelativeUrlWithScheme => -13,
+// ParseError::RelativeUrlWithoutBase => -14,
+// ParseError::RelativeUrlWithNonRelativeBase => -15,
+// ParseError::NonAsciiDomainsNotSupportedYet => -16,
+// ParseError::CannotSetJavascriptFragment => -17,
+// ParseError::CannotSetPortWithFileLikeScheme => -18,
+// ParseError::CannotSetUsernameWithNonRelativeScheme => -19,
+// ParseError::CannotSetPasswordWithNonRelativeScheme => -20,
+// ParseError::CannotSetHostPortWithNonRelativeScheme => -21,
+// ParseError::CannotSetHostWithNonRelativeScheme => -22,
+// ParseError::CannotSetPortWithNonRelativeScheme => -23,
+// ParseError::CannotSetPathWithNonRelativeScheme => -24,
+// }
+ }
+}
+
+pub enum NSError {
+ OK,
+ InvalidArg,
+ Failure,
+}
+
+impl ErrorCode for NSError {
+ #[allow(overflowing_literals)]
+ fn error_code(&self) -> i32 {
+ match *self {
+ NSError::OK => 0,
+ NSError::InvalidArg => 0x80070057,
+ NSError::Failure => 0x80004005
+ }
+ }
+}
diff --git a/netwerk/base/rust-url-capi/src/lib.rs b/netwerk/base/rust-url-capi/src/lib.rs
new file mode 100644
index 000000000..e2997ce46
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/lib.rs
@@ -0,0 +1,477 @@
+extern crate url;
+use url::{Url, ParseError, ParseOptions};
+use url::quirks;
+extern crate libc;
+use libc::size_t;
+
+
+use std::mem;
+use std::str;
+
+#[allow(non_camel_case_types)]
+pub type rusturl_ptr = *const libc::c_void;
+
+mod string_utils;
+pub use string_utils::*;
+
+mod error_mapping;
+use error_mapping::*;
+
+fn parser<'a>() -> ParseOptions<'a> {
+ Url::options()
+}
+
+fn default_port(scheme: &str) -> Option<u32> {
+ match scheme {
+ "ftp" => Some(21),
+ "gopher" => Some(70),
+ "http" => Some(80),
+ "https" => Some(443),
+ "ws" => Some(80),
+ "wss" => Some(443),
+ "rtsp" => Some(443),
+ "moz-anno" => Some(443),
+ "android" => Some(443),
+ _ => None,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_new(spec: *mut libc::c_char, len: size_t) -> rusturl_ptr {
+ let slice = std::slice::from_raw_parts(spec as *const libc::c_uchar, len as usize);
+ let url_spec = match str::from_utf8(slice) {
+ Ok(spec) => spec,
+ Err(_) => return 0 as rusturl_ptr
+ };
+
+ let url = match parser().parse(url_spec) {
+ Ok(url) => url,
+ Err(_) => return 0 as rusturl_ptr
+ };
+
+ let url = Box::new(url);
+ Box::into_raw(url) as rusturl_ptr
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_free(urlptr: rusturl_ptr) {
+ if urlptr.is_null() {
+ return ();
+ }
+ let url: Box<Url> = Box::from_raw(urlptr as *mut url::Url);
+ drop(url);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_spec(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ cont.assign(&url.to_string())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_scheme(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ cont.assign(&url.scheme())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_username(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ cont.set_size(0)
+ } else {
+ cont.assign(url.username())
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_password(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ match url.password() {
+ Some(p) => cont.assign(&p.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_host(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.host() {
+ Some(h) => cont.assign(&h.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_port(urlptr: rusturl_ptr) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.port() {
+ Some(port) => port as i32,
+ None => -1
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_path(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ cont.set_size(0)
+ } else {
+ cont.assign(url.path())
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_query(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+ match url.query() {
+ Some(ref s) => cont.assign(s),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_get_fragment(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.fragment() {
+ Some(ref fragment) => cont.assign(fragment),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_has_fragment(urlptr: rusturl_ptr) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &Url = mem::transmute(urlptr);
+
+ match url.fragment() {
+ Some(_) => return 1,
+ None => return 0
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_scheme(urlptr: rusturl_ptr, scheme: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(scheme as *const libc::c_uchar, len as usize);
+
+ let scheme_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_protocol(url, scheme_).error_code()
+}
+
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_username(urlptr: rusturl_ptr, username: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(username as *const libc::c_uchar, len as usize);
+
+ let username_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_username(url, username_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_password(urlptr: rusturl_ptr, password: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(password as *const libc::c_uchar, len as usize);
+
+ let password_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_password(url, password_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_host_and_port(urlptr: rusturl_ptr, host_and_port: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(host_and_port as *const libc::c_uchar, len as usize);
+
+ let host_and_port_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_host(url, host_and_port_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_host(urlptr: rusturl_ptr, host: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(host as *const libc::c_uchar, len as usize);
+
+ let hostname = match str::from_utf8(slice).ok() {
+ Some(h) => h,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_hostname(url, hostname).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_port(urlptr: rusturl_ptr, port: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(port as *const libc::c_uchar, len as usize);
+
+ let port_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_port(url, port_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_port_no(urlptr: rusturl_ptr, new_port: i32) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ if url.cannot_be_a_base() {
+ -100
+ } else {
+ if url.scheme() == "file" {
+ return -100;
+ }
+ match default_port(url.scheme()) {
+ Some(def_port) => if new_port == def_port as i32 {
+ let _ = url.set_port(None);
+ return NSError::OK.error_code();
+ },
+ None => {}
+ };
+ if new_port > std::u16::MAX as i32 || new_port < 0 {
+ let _ = url.set_port(None);
+ } else {
+ let _ = url.set_port(Some(new_port as u16));
+ }
+ NSError::OK.error_code()
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_path(urlptr: rusturl_ptr, path: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(path as *const libc::c_uchar, len as usize);
+
+ let path_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_pathname(url, path_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_query(urlptr: rusturl_ptr, query: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(query as *const libc::c_uchar, len as usize);
+
+ let query_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_search(url, query_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_set_fragment(urlptr: rusturl_ptr, fragment: *mut libc::c_char, len: size_t) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let mut url: &mut Url = mem::transmute(urlptr);
+ let slice = std::slice::from_raw_parts(fragment as *const libc::c_uchar, len as usize);
+
+ let fragment_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed
+ };
+
+ quirks::set_hash(url, fragment_).error_code()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_resolve(urlptr: rusturl_ptr, resolve: *mut libc::c_char, len: size_t, cont: *mut libc::c_void) -> i32 {
+ if urlptr.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url: &mut Url = mem::transmute(urlptr);
+
+ let slice = std::slice::from_raw_parts(resolve as *const libc::c_uchar, len as usize);
+
+ let resolve_ = match str::from_utf8(slice).ok() {
+ Some(p) => p,
+ None => return NSError::Failure.error_code()
+ };
+
+ match parser().base_url(Some(&url)).parse(resolve_).ok() {
+ Some(u) => cont.assign(&u.to_string()),
+ None => cont.set_size(0)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_common_base_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr1.is_null() || urlptr2.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url1: &Url = mem::transmute(urlptr1);
+ let url2: &Url = mem::transmute(urlptr2);
+
+ if url1 == url2 {
+ return cont.assign(&url1.to_string());
+ }
+
+ if url1.scheme() != url2.scheme() ||
+ url1.host() != url2.host() ||
+ url1.username() != url2.username() ||
+ url1.password() != url2.password() ||
+ url1.port() != url2.port() {
+ return cont.set_size(0);
+ }
+
+ let path1 = match url1.path_segments() {
+ Some(path) => path,
+ None => return cont.set_size(0)
+ };
+ let path2 = match url2.path_segments() {
+ Some(path) => path,
+ None => return cont.set_size(0)
+ };
+
+ let mut url = url1.clone();
+ url.set_query(None);
+ let _ = url.set_host(None);
+ {
+ let mut new_segments = if let Ok(segments) = url.path_segments_mut() {
+ segments
+ } else {
+ return cont.set_size(0)
+ };
+
+ for (p1, p2) in path1.zip(path2) {
+ if p1 != p2 {
+ break;
+ } else {
+ new_segments.push(p1);
+ }
+ }
+ }
+
+ cont.assign(&url.to_string())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rusturl_relative_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 {
+ if urlptr1.is_null() || urlptr2.is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ let url1: &Url = mem::transmute(urlptr1);
+ let url2: &Url = mem::transmute(urlptr2);
+
+ if url1 == url2 {
+ return cont.set_size(0);
+ }
+
+ if url1.scheme() != url2.scheme() ||
+ url1.host() != url2.host() ||
+ url1.username() != url2.username() ||
+ url1.password() != url2.password() ||
+ url1.port() != url2.port() {
+ return cont.assign(&url2.to_string());
+ }
+
+ let mut path1 = match url1.path_segments() {
+ Some(path) => path,
+ None => return cont.assign(&url2.to_string())
+ };
+ let mut path2 = match url2.path_segments() {
+ Some(path) => path,
+ None => return cont.assign(&url2.to_string())
+ };
+
+ // TODO: file:// on WIN?
+
+ // Exhaust the part of the iterators that match
+ while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) {
+ if p1 != p2 {
+ break;
+ }
+ }
+
+ let mut buffer: String = "".to_string();
+ for _ in path1 {
+ buffer = buffer + "../";
+ }
+ for p2 in path2 {
+ buffer = buffer + p2 + "/";
+ }
+
+ return cont.assign(&buffer);
+}
+
diff --git a/netwerk/base/rust-url-capi/src/rust-url-capi.h b/netwerk/base/rust-url-capi/src/rust-url-capi.h
new file mode 100644
index 000000000..8d7a05aed
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/rust-url-capi.h
@@ -0,0 +1,45 @@
+#ifndef __RUST_URL_CAPI
+#define __RUST_URL_CAPI
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rusturl;
+typedef struct rusturl* rusturl_ptr;
+
+rusturl_ptr rusturl_new(const char *spec, size_t src_len);
+void rusturl_free(rusturl_ptr url);
+
+int32_t rusturl_get_spec(rusturl_ptr url, void*);
+int32_t rusturl_get_scheme(rusturl_ptr url, void*);
+int32_t rusturl_get_username(rusturl_ptr url, void*);
+int32_t rusturl_get_password(rusturl_ptr url, void*);
+int32_t rusturl_get_host(rusturl_ptr url, void*);
+int32_t rusturl_get_port(rusturl_ptr url); // returns port or -1
+int32_t rusturl_get_path(rusturl_ptr url, void*);
+int32_t rusturl_get_query(rusturl_ptr url, void*);
+int32_t rusturl_get_fragment(rusturl_ptr url, void*);
+int32_t rusturl_has_fragment(rusturl_ptr url); // 1 true, 0 false, < 0 error
+
+int32_t rusturl_set_scheme(rusturl_ptr url, const char *scheme, size_t len);
+int32_t rusturl_set_username(rusturl_ptr url, const char *user, size_t len);
+int32_t rusturl_set_password(rusturl_ptr url, const char *pass, size_t len);
+int32_t rusturl_set_host_and_port(rusturl_ptr url, const char *hostport, size_t len);
+int32_t rusturl_set_host(rusturl_ptr url, const char *host, size_t len);
+int32_t rusturl_set_port(rusturl_ptr url, const char *port, size_t len);
+int32_t rusturl_set_port_no(rusturl_ptr url, const int32_t port);
+int32_t rusturl_set_path(rusturl_ptr url, const char *path, size_t len);
+int32_t rusturl_set_query(rusturl_ptr url, const char *path, size_t len);
+int32_t rusturl_set_fragment(rusturl_ptr url, const char *path, size_t len);
+
+int32_t rusturl_resolve(rusturl_ptr url, const char *relative, size_t len, void*);
+int32_t rusturl_common_base_spec(rusturl_ptr url1, rusturl_ptr url2, void*);
+int32_t rusturl_relative_spec(rusturl_ptr url1, rusturl_ptr url2, void*);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __RUST_URL_CAPI \ No newline at end of file
diff --git a/netwerk/base/rust-url-capi/src/string_utils.rs b/netwerk/base/rust-url-capi/src/string_utils.rs
new file mode 100644
index 000000000..ae68a60dc
--- /dev/null
+++ b/netwerk/base/rust-url-capi/src/string_utils.rs
@@ -0,0 +1,57 @@
+extern crate libc;
+use libc::size_t;
+
+extern crate std;
+use std::ptr;
+
+use error_mapping::*;
+
+extern "C" {
+ fn c_fn_set_size(user: *mut libc::c_void, size: size_t) -> i32;
+ fn c_fn_get_buffer(user: *mut libc::c_void) -> *mut libc::c_char;
+}
+
+pub trait StringContainer {
+ fn set_size(&self, size_t) -> i32;
+ fn get_buffer(&self) -> *mut libc::c_char;
+ fn assign(&self, content: &str) -> i32;
+}
+
+impl StringContainer for *mut libc::c_void {
+ fn set_size(&self, size: size_t) -> i32 {
+ if (*self).is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+ unsafe {
+ c_fn_set_size(*self, size);
+ }
+
+ return NSError::OK.error_code();
+ }
+ fn get_buffer(&self) -> *mut libc::c_char {
+ if (*self).is_null() {
+ return 0 as *mut libc::c_char;
+ }
+ unsafe {
+ c_fn_get_buffer(*self)
+ }
+ }
+ fn assign(&self, content: &str) -> i32 {
+ if (*self).is_null() {
+ return NSError::InvalidArg.error_code();
+ }
+
+ unsafe {
+ let slice = content.as_bytes();
+ c_fn_set_size(*self, slice.len());
+ let buf = c_fn_get_buffer(*self);
+ if buf.is_null() {
+ return NSError::Failure.error_code();
+ }
+
+ ptr::copy(slice.as_ptr(), buf as *mut u8, slice.len());
+ }
+
+ NSError::OK.error_code()
+ }
+}
diff --git a/netwerk/base/rust-url-capi/test/Makefile b/netwerk/base/rust-url-capi/test/Makefile
new file mode 100644
index 000000000..a4e2fd0cf
--- /dev/null
+++ b/netwerk/base/rust-url-capi/test/Makefile
@@ -0,0 +1,4 @@
+all:
+ cd .. && cargo build
+ g++ -Wall -o test test.cpp ../target/debug/librust*.a -ldl -lpthread -lrt -lgcc_s -lpthread -lc -lm -std=c++0x
+ ./test
diff --git a/netwerk/base/rust-url-capi/test/test.cpp b/netwerk/base/rust-url-capi/test/test.cpp
new file mode 100644
index 000000000..6e90ea43b
--- /dev/null
+++ b/netwerk/base/rust-url-capi/test/test.cpp
@@ -0,0 +1,141 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "../src/rust-url-capi.h"
+
+class StringContainer
+{
+public:
+ StringContainer()
+ {
+ mBuffer = nullptr;
+ mLength = 0;
+ }
+
+ ~StringContainer()
+ {
+ free(mBuffer);
+ mBuffer = nullptr;
+ }
+
+ void SetSize(size_t size)
+ {
+ mLength = size;
+ if (mBuffer) {
+ mBuffer = (char *)realloc(mBuffer, size);
+ return;
+ }
+ mBuffer = (char *)malloc(size);
+ }
+
+ char * GetBuffer()
+ {
+ return mBuffer;
+ }
+
+ void CheckEquals(const char * ref) {
+ int32_t refLen = strlen(ref);
+ printf("CheckEquals: %s (len:%d)\n", ref, refLen);
+ if (refLen != mLength || strncmp(mBuffer, ref, mLength)) {
+ printf("\t--- ERROR ---\n");
+ printf("Got : ");
+ fwrite(mBuffer, mLength, 1, stdout);
+ printf(" (len:%d)\n", mLength);
+ exit(-1);
+ }
+ printf("-> OK\n");
+ }
+private:
+ int32_t mLength;
+ char * mBuffer;
+};
+
+extern "C" int32_t c_fn_set_size(void * container, size_t size)
+{
+ ((StringContainer *) container)->SetSize(size);
+ return 0;
+}
+
+extern "C" char * c_fn_get_buffer(void * container)
+{
+ return ((StringContainer *) container)->GetBuffer();
+}
+
+#define TEST_CALL(func, expected) \
+{ \
+ int32_t code = func; \
+ printf("%s -> code %d\n", #func, code); \
+ assert(code == expected); \
+ printf("-> OK\n"); \
+} \
+
+
+int main() {
+ // Create URL
+ rusturl_ptr url = rusturl_new("http://example.com/path/some/file.txt",
+ strlen("http://example.com/path/some/file.txt"));
+ assert(url); // Check we have a URL
+
+ StringContainer container;
+
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.com/path/some/file.txt");
+ TEST_CALL(rusturl_set_host(url, "test.com", strlen("test.com")), 0);
+ TEST_CALL(rusturl_get_host(url, &container), 0);
+ container.CheckEquals("test.com");
+ TEST_CALL(rusturl_get_path(url, &container), 0);
+ container.CheckEquals("/path/some/file.txt");
+ TEST_CALL(rusturl_set_path(url, "hello/../else.txt", strlen("hello/../else.txt")), 0);
+ TEST_CALL(rusturl_get_path(url, &container), 0);
+ container.CheckEquals("/else.txt");
+ TEST_CALL(rusturl_resolve(url, "./bla/file.txt", strlen("./bla/file.txt"), &container), 0);
+ container.CheckEquals("http://test.com/bla/file.txt");
+ TEST_CALL(rusturl_get_scheme(url, &container), 0);
+ container.CheckEquals("http");
+ TEST_CALL(rusturl_set_username(url, "user", strlen("user")), 0);
+ TEST_CALL(rusturl_get_username(url, &container), 0);
+ container.CheckEquals("user");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://user@test.com/else.txt");
+ TEST_CALL(rusturl_set_password(url, "pass", strlen("pass")), 0);
+ TEST_CALL(rusturl_get_password(url, &container), 0);
+ container.CheckEquals("pass");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://user:pass@test.com/else.txt");
+ TEST_CALL(rusturl_set_username(url, "", strlen("")), 0);
+ TEST_CALL(rusturl_set_password(url, "", strlen("")), 0);
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://test.com/else.txt");
+ TEST_CALL(rusturl_set_host_and_port(url, "example.org:1234", strlen("example.org:1234")), 0);
+ TEST_CALL(rusturl_get_host(url, &container), 0);
+ container.CheckEquals("example.org");
+ assert(rusturl_get_port(url) == 1234);
+ TEST_CALL(rusturl_set_port(url, "9090", strlen("9090")), 0);
+ assert(rusturl_get_port(url) == 9090);
+ TEST_CALL(rusturl_set_query(url, "x=1", strlen("x=1")), 0);
+ TEST_CALL(rusturl_get_query(url, &container), 0);
+ container.CheckEquals("x=1");
+ TEST_CALL(rusturl_set_fragment(url, "fragment", strlen("fragment")), 0);
+ TEST_CALL(rusturl_get_fragment(url, &container), 0);
+ container.CheckEquals("fragment");
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.org:9090/else.txt?x=1#fragment");
+
+ // Free the URL
+ rusturl_free(url);
+
+ url = rusturl_new("http://example.com/#",
+ strlen("http://example.com/#"));
+ assert(url); // Check we have a URL
+
+ assert(rusturl_has_fragment(url) == 1);
+ TEST_CALL(rusturl_set_fragment(url, "", 0), 0);
+ assert(rusturl_has_fragment(url) == 0);
+ TEST_CALL(rusturl_get_spec(url, &container), 0);
+ container.CheckEquals("http://example.com/");
+
+ rusturl_free(url);
+
+ printf("SUCCESS\n");
+ return 0;
+} \ No newline at end of file
diff --git a/netwerk/base/security-prefs.js b/netwerk/base/security-prefs.js
new file mode 100644
index 000000000..9f42745f7
--- /dev/null
+++ b/netwerk/base/security-prefs.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pref("security.tls.version.min", 1);
+pref("security.tls.version.max", 3);
+pref("security.tls.version.fallback-limit", 3);
+pref("security.tls.insecure_fallback_hosts", "");
+pref("security.tls.unrestricted_rc4_fallback", false);
+pref("security.tls.enable_0rtt_data", false);
+
+pref("security.ssl.treat_unsafe_negotiation_as_broken", false);
+pref("security.ssl.require_safe_negotiation", false);
+pref("security.ssl.enable_ocsp_stapling", true);
+pref("security.ssl.enable_false_start", true);
+pref("security.ssl.false_start.require-npn", false);
+pref("security.ssl.enable_npn", true);
+pref("security.ssl.enable_alpn", true);
+
+pref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", true);
+pref("security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", true);
+pref("security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", true);
+pref("security.ssl3.ecdhe_rsa_aes_128_sha", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_128_sha", true);
+pref("security.ssl3.ecdhe_rsa_aes_256_sha", true);
+pref("security.ssl3.ecdhe_ecdsa_aes_256_sha", true);
+pref("security.ssl3.dhe_rsa_aes_128_sha", true);
+pref("security.ssl3.dhe_rsa_aes_256_sha", true);
+pref("security.ssl3.rsa_aes_128_sha", true);
+pref("security.ssl3.rsa_aes_256_sha", true);
+pref("security.ssl3.rsa_des_ede3_sha", true);
+
+pref("security.content.signature.root_hash",
+ "97:E8:BA:9C:F1:2F:B3:DE:53:CC:42:A4:E6:57:7E:D6:4D:F4:93:C2:47:B4:14:FE:A0:36:81:8D:38:23:56:0E");
+
+pref("security.default_personal_cert", "Ask Every Time");
+pref("security.remember_cert_checkbox_default_setting", true);
+pref("security.ask_for_password", 0);
+pref("security.password_lifetime", 30);
+
+// The supported values of this pref are:
+// 0: disable detecting Family Safety mode and importing the root
+// 1: only attempt to detect Family Safety mode (don't import the root)
+// 2: detect Family Safety mode and import the root
+// (This is only relevant to Windows 8.1)
+pref("security.family_safety.mode", 2);
+
+pref("security.enterprise_roots.enabled", false);
+
+pref("security.OCSP.enabled", 1);
+pref("security.OCSP.require", false);
+pref("security.OCSP.GET.enabled", false);
+
+pref("security.pki.cert_short_lifetime_in_days", 10);
+// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
+// See the comment in CertVerifier.cpp.
+// 3 = only allow SHA-1 for certificates issued by an imported root.
+pref("security.pki.sha1_enforcement_level", 3);
+
+// security.pki.name_matching_mode controls how the platform matches hostnames
+// to name information in TLS certificates. The possible values are:
+// 0: always fall back to the subject common name if necessary (as in, if the
+// subject alternative name extension is either not present or does not
+// contain any DNS names or IP addresses)
+// 1: fall back to the subject common name for certificates valid before 23
+// August 2016 if necessary
+// 2: fall back to the subject common name for certificates valid before 23
+// August 2015 if necessary
+// 3: only use name information from the subject alternative name extension
+#ifdef RELEASE_OR_BETA
+pref("security.pki.name_matching_mode", 1);
+#else
+pref("security.pki.name_matching_mode", 2);
+#endif
+
+// security.pki.netscape_step_up_policy controls how the platform handles the
+// id-Netscape-stepUp OID in extended key usage extensions of CA certificates.
+// 0: id-Netscape-stepUp is always considered equivalent to id-kp-serverAuth
+// 1: it is considered equivalent when the notBefore is before 23 August 2016
+// 2: similarly, but for 23 August 2015
+// 3: it is never considered equivalent
+#ifdef RELEASE_OR_BETA
+pref("security.pki.netscape_step_up_policy", 1);
+#else
+pref("security.pki.netscape_step_up_policy", 2);
+#endif
+
+// Configures Certificate Transparency support mode:
+// 0: Fully disabled.
+// 1: Only collect telemetry. CT qualification checks are not performed.
+pref("security.pki.certificate_transparency.mode", 0);
+
+pref("security.webauth.u2f", false);
+pref("security.webauth.u2f_enable_softtoken", false);
+pref("security.webauth.u2f_enable_usbtoken", false);
+
+pref("security.ssl.errorReporting.enabled", true);
+pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
+pref("security.ssl.errorReporting.automatic", false);
+
+// Impose a maximum age on HPKP headers, to avoid sites getting permanently
+// blacking themselves out by setting a bad pin. (60 days by default)
+// https://tools.ietf.org/html/rfc7469#section-4.1
+pref("security.cert_pinning.max_max_age_seconds", 5184000);
+
+// If a request is mixed-content, send an HSTS priming request to attempt to
+// see if it is available over HTTPS.
+pref("security.mixed_content.send_hsts_priming", true);
+#ifdef RELEASE_OR_BETA
+// Don't change the order of evaluation of mixed-content and HSTS upgrades
+pref("security.mixed_content.use_hsts", false);
+#else
+// Change the order of evaluation so HSTS upgrades happen before
+// mixed-content blocking
+pref("security.mixed_content.use_hsts", true);
+#endif