From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- netwerk/base/ARefBase.h | 31 + netwerk/base/ArrayBufferInputStream.cpp | 132 + netwerk/base/ArrayBufferInputStream.h | 40 + netwerk/base/AutoClose.h | 62 + netwerk/base/BackgroundFileSaver.cpp | 1121 ++++++ netwerk/base/BackgroundFileSaver.h | 397 ++ netwerk/base/CacheInfoIPCTypes.h | 24 + netwerk/base/CaptivePortalService.cpp | 428 +++ netwerk/base/CaptivePortalService.h | 79 + netwerk/base/Dashboard.cpp | 1182 ++++++ netwerk/base/Dashboard.h | 91 + netwerk/base/DashboardTypes.h | 175 + netwerk/base/DefaultURI.cpp | 538 +++ netwerk/base/DefaultURI.h | 59 + netwerk/base/EventTokenBucket.cpp | 410 ++ netwerk/base/EventTokenBucket.h | 155 + netwerk/base/FuzzyLayer.cpp | 407 ++ netwerk/base/FuzzyLayer.h | 29 + netwerk/base/FuzzySecurityInfo.cpp | 177 + netwerk/base/FuzzySecurityInfo.h | 30 + netwerk/base/FuzzySocketControl.cpp | 185 + netwerk/base/FuzzySocketControl.h | 29 + netwerk/base/IOActivityMonitor.cpp | 495 +++ netwerk/base/IOActivityMonitor.h | 83 + netwerk/base/IPv6Utils.h | 50 + netwerk/base/InterceptionInfo.cpp | 63 + netwerk/base/InterceptionInfo.h | 44 + netwerk/base/LoadContextInfo.cpp | 168 + netwerk/base/LoadContextInfo.h | 54 + netwerk/base/LoadInfo.cpp | 2282 +++++++++++ netwerk/base/LoadInfo.h | 392 ++ netwerk/base/LoadTainting.h | 31 + netwerk/base/MemoryDownloader.cpp | 71 + netwerk/base/MemoryDownloader.h | 59 + netwerk/base/NetUtil.sys.mjs | 453 +++ netwerk/base/NetworkConnectivityService.cpp | 552 +++ netwerk/base/NetworkConnectivityService.h | 76 + netwerk/base/NetworkDataCountLayer.cpp | 138 + netwerk/base/NetworkDataCountLayer.h | 28 + netwerk/base/NetworkInfoServiceCocoa.cpp | 100 + netwerk/base/NetworkInfoServiceImpl.h | 18 + netwerk/base/NetworkInfoServiceLinux.cpp | 95 + netwerk/base/NetworkInfoServiceWindows.cpp | 58 + netwerk/base/PollableEvent.cpp | 399 ++ netwerk/base/PollableEvent.h | 67 + netwerk/base/Predictor.cpp | 2439 ++++++++++++ netwerk/base/Predictor.h | 458 +++ netwerk/base/PrivateBrowsingChannel.h | 118 + netwerk/base/ProtocolHandlerInfo.cpp | 86 + netwerk/base/ProtocolHandlerInfo.h | 67 + netwerk/base/ProxyAutoConfig.cpp | 940 +++++ netwerk/base/ProxyAutoConfig.h | 155 + netwerk/base/ProxyConfig.h | 199 + netwerk/base/RedirectChannelRegistrar.cpp | 86 + netwerk/base/RedirectChannelRegistrar.h | 47 + netwerk/base/RequestContextService.cpp | 622 +++ netwerk/base/RequestContextService.h | 44 + netwerk/base/SSLTokensCache.cpp | 570 +++ netwerk/base/SSLTokensCache.h | 123 + netwerk/base/ShutdownLayer.cpp | 76 + netwerk/base/ShutdownLayer.h | 23 + netwerk/base/SimpleBuffer.cpp | 85 + netwerk/base/SimpleBuffer.h | 57 + netwerk/base/SimpleChannel.cpp | 136 + netwerk/base/SimpleChannel.h | 152 + netwerk/base/SimpleChannelParent.cpp | 97 + netwerk/base/SimpleChannelParent.h | 40 + netwerk/base/TLSServerSocket.cpp | 446 +++ netwerk/base/TLSServerSocket.h | 81 + netwerk/base/TRRLoadInfo.cpp | 821 ++++ netwerk/base/TRRLoadInfo.h | 53 + netwerk/base/ThrottleQueue.cpp | 410 ++ netwerk/base/ThrottleQueue.h | 66 + netwerk/base/Tickler.cpp | 249 ++ netwerk/base/Tickler.h | 132 + netwerk/base/ascii_pac_utils.js | 256 ++ netwerk/base/http-sfv/Cargo.toml | 15 + netwerk/base/http-sfv/SFVService.cpp | 39 + netwerk/base/http-sfv/SFVService.h | 14 + netwerk/base/http-sfv/moz.build | 17 + netwerk/base/http-sfv/nsIStructuredFieldValues.idl | 290 ++ netwerk/base/http-sfv/src/lib.rs | 873 +++++ netwerk/base/makecppstring.py | 17 + netwerk/base/moz.build | 336 ++ netwerk/base/mozIThirdPartyUtil.idl | 231 ++ netwerk/base/mozurl/.gitignore | 2 + netwerk/base/mozurl/Cargo.toml | 12 + netwerk/base/mozurl/MozURL.cpp | 12 + netwerk/base/mozurl/MozURL.h | 231 ++ netwerk/base/mozurl/cbindgen.toml | 61 + netwerk/base/mozurl/moz.build | 22 + netwerk/base/mozurl/src/lib.rs | 564 +++ netwerk/base/netCore.h | 14 + netwerk/base/nsASocketHandler.h | 102 + netwerk/base/nsAsyncRedirectVerifyHelper.cpp | 279 ++ netwerk/base/nsAsyncRedirectVerifyHelper.h | 121 + netwerk/base/nsAsyncStreamCopier.cpp | 405 ++ netwerk/base/nsAsyncStreamCopier.h | 76 + netwerk/base/nsAuthInformationHolder.cpp | 61 + netwerk/base/nsAuthInformationHolder.h | 44 + netwerk/base/nsBase64Encoder.cpp | 54 + netwerk/base/nsBase64Encoder.h | 33 + netwerk/base/nsBaseChannel.cpp | 951 +++++ netwerk/base/nsBaseChannel.h | 310 ++ netwerk/base/nsBaseContentStream.cpp | 127 + netwerk/base/nsBaseContentStream.h | 79 + netwerk/base/nsBufferedStreams.cpp | 1197 ++++++ netwerk/base/nsBufferedStreams.h | 165 + netwerk/base/nsDNSPrefetch.cpp | 164 + netwerk/base/nsDNSPrefetch.h | 72 + netwerk/base/nsDirectoryIndexStream.cpp | 304 ++ netwerk/base/nsDirectoryIndexStream.h | 46 + netwerk/base/nsDownloader.cpp | 98 + netwerk/base/nsDownloader.h | 36 + netwerk/base/nsFileStreams.cpp | 1031 +++++ netwerk/base/nsFileStreams.h | 292 ++ netwerk/base/nsIArrayBufferInputStream.idl | 25 + netwerk/base/nsIAsyncStreamCopier.idl | 64 + netwerk/base/nsIAsyncStreamCopier2.idl | 59 + netwerk/base/nsIAsyncVerifyRedirectCallback.idl | 19 + netwerk/base/nsIAuthInformation.idl | 117 + netwerk/base/nsIAuthModule.idl | 146 + netwerk/base/nsIAuthPrompt.idl | 121 + netwerk/base/nsIAuthPrompt2.idl | 104 + netwerk/base/nsIAuthPromptAdapterFactory.idl | 22 + netwerk/base/nsIAuthPromptCallback.idl | 43 + netwerk/base/nsIAuthPromptProvider.idl | 34 + netwerk/base/nsIBackgroundFileSaver.idl | 183 + netwerk/base/nsIBufferedStreams.idl | 47 + netwerk/base/nsIByteRangeRequest.idl | 27 + netwerk/base/nsICacheInfoChannel.idl | 207 + netwerk/base/nsICachingChannel.idl | 110 + netwerk/base/nsICancelable.idl | 24 + netwerk/base/nsICaptivePortalService.idl | 83 + netwerk/base/nsIChannel.idl | 405 ++ netwerk/base/nsIChannelEventSink.idl | 104 + netwerk/base/nsIChildChannel.idl | 34 + netwerk/base/nsIClassOfService.idl | 103 + netwerk/base/nsIClassifiedChannel.idl | 201 + netwerk/base/nsIContentSniffer.idl | 35 + netwerk/base/nsIDHCPClient.idl | 19 + netwerk/base/nsIDashboard.idl | 64 + netwerk/base/nsIDashboardEventNotifier.idl | 23 + netwerk/base/nsIDownloader.idl | 50 + netwerk/base/nsIEncodedChannel.idl | 55 + netwerk/base/nsIExternalProtocolHandler.idl | 17 + netwerk/base/nsIFileStreams.idl | 239 ++ netwerk/base/nsIFileURL.idl | 42 + netwerk/base/nsIForcePendingChannel.idl | 22 + netwerk/base/nsIFormPOSTActionChannel.idl | 17 + netwerk/base/nsIHttpAuthenticatorCallback.idl | 30 + netwerk/base/nsIHttpPushListener.idl | 36 + netwerk/base/nsIIOService.idl | 354 ++ netwerk/base/nsIIncrementalDownload.idl | 109 + netwerk/base/nsIIncrementalStreamLoader.idl | 100 + netwerk/base/nsIInputStreamChannel.idl | 64 + netwerk/base/nsIInputStreamPump.idl | 66 + netwerk/base/nsIInterceptionInfo.idl | 73 + netwerk/base/nsILoadContextInfo.idl | 86 + netwerk/base/nsILoadGroup.idl | 105 + netwerk/base/nsILoadGroupChild.idl | 41 + netwerk/base/nsILoadInfo.idl | 1488 ++++++++ netwerk/base/nsIMIMEInputStream.idl | 48 + netwerk/base/nsIMultiPartChannel.idl | 51 + netwerk/base/nsINestedURI.idl | 75 + netwerk/base/nsINetAddr.idl | 88 + netwerk/base/nsINetUtil.idl | 223 ++ netwerk/base/nsINetworkConnectivityService.idl | 44 + netwerk/base/nsINetworkInfoService.idl | 56 + netwerk/base/nsINetworkInterceptController.idl | 245 ++ netwerk/base/nsINetworkLinkService.idl | 154 + netwerk/base/nsINetworkPredictor.idl | 194 + netwerk/base/nsINetworkPredictorVerifier.idl | 40 + netwerk/base/nsINullChannel.idl | 16 + netwerk/base/nsIOService.cpp | 2201 +++++++++++ netwerk/base/nsIOService.h | 281 ++ netwerk/base/nsIParentChannel.idl | 76 + netwerk/base/nsIParentRedirectingChannel.idl | 68 + netwerk/base/nsIPermission.idl | 82 + netwerk/base/nsIPermissionManager.idl | 242 ++ netwerk/base/nsIPrivateBrowsingChannel.idl | 58 + netwerk/base/nsIProgressEventSink.idl | 72 + netwerk/base/nsIPrompt.idl | 101 + netwerk/base/nsIProtocolHandler.idl | 311 ++ netwerk/base/nsIProtocolProxyCallback.idl | 42 + netwerk/base/nsIProtocolProxyFilter.idl | 97 + netwerk/base/nsIProtocolProxyService.idl | 330 ++ netwerk/base/nsIProtocolProxyService2.idl | 32 + netwerk/base/nsIProxiedChannel.idl | 36 + netwerk/base/nsIProxiedProtocolHandler.idl | 36 + netwerk/base/nsIProxyInfo.idl | 106 + netwerk/base/nsIRandomGenerator.idl | 24 + netwerk/base/nsIRedirectChannelRegistrar.idl | 71 + netwerk/base/nsIRedirectHistoryEntry.idl | 34 + netwerk/base/nsIRedirectResultListener.idl | 22 + netwerk/base/nsIRequest.idl | 341 ++ netwerk/base/nsIRequestContext.idl | 158 + netwerk/base/nsIRequestObserver.idl | 37 + netwerk/base/nsIRequestObserverProxy.idl | 31 + netwerk/base/nsIResumableChannel.idl | 39 + netwerk/base/nsISecCheckWrapChannel.idl | 24 + netwerk/base/nsISecureBrowserUI.idl | 21 + netwerk/base/nsISensitiveInfoHiddenURI.idl | 17 + netwerk/base/nsISerializationHelper.idl | 29 + netwerk/base/nsIServerSocket.idl | 269 ++ netwerk/base/nsISimpleStreamListener.idl | 25 + netwerk/base/nsISimpleURIMutator.idl | 15 + netwerk/base/nsISocketFilter.idl | 53 + netwerk/base/nsISocketTransport.idl | 382 ++ netwerk/base/nsISocketTransportService.idl | 162 + netwerk/base/nsISpeculativeConnect.idl | 98 + netwerk/base/nsIStandardURL.idl | 85 + netwerk/base/nsIStreamListener.idl | 38 + netwerk/base/nsIStreamListenerTee.idl | 50 + netwerk/base/nsIStreamLoader.idl | 77 + netwerk/base/nsIStreamTransportService.idl | 49 + netwerk/base/nsISyncStreamListener.idl | 19 + netwerk/base/nsISystemProxySettings.idl | 42 + netwerk/base/nsITLSServerSocket.idl | 183 + netwerk/base/nsIThreadRetargetableRequest.idl | 43 + .../base/nsIThreadRetargetableStreamListener.idl | 32 + netwerk/base/nsIThrottledInputChannel.idl | 87 + netwerk/base/nsITimedChannel.idl | 128 + netwerk/base/nsITraceableChannel.idl | 37 + netwerk/base/nsITransport.idl | 162 + netwerk/base/nsIUDPSocket.idl | 406 ++ netwerk/base/nsIURI.idl | 322 ++ netwerk/base/nsIURIMutator.idl | 540 +++ netwerk/base/nsIURIMutatorUtils.cpp | 27 + netwerk/base/nsIURIWithSpecialOrigin.idl | 20 + netwerk/base/nsIURL.idl | 130 + netwerk/base/nsIURLParser.idl | 98 + netwerk/base/nsIUploadChannel.idl | 56 + netwerk/base/nsIUploadChannel2.idl | 57 + netwerk/base/nsIncrementalDownload.cpp | 878 +++++ netwerk/base/nsIncrementalStreamLoader.cpp | 185 + netwerk/base/nsIncrementalStreamLoader.h | 54 + netwerk/base/nsInputStreamChannel.cpp | 101 + netwerk/base/nsInputStreamChannel.h | 43 + netwerk/base/nsInputStreamPump.cpp | 786 ++++ netwerk/base/nsInputStreamPump.h | 129 + netwerk/base/nsLoadGroup.cpp | 1112 ++++++ netwerk/base/nsLoadGroup.h | 111 + netwerk/base/nsMIMEInputStream.cpp | 500 +++ netwerk/base/nsMIMEInputStream.h | 27 + netwerk/base/nsMediaFragmentURIParser.cpp | 354 ++ netwerk/base/nsMediaFragmentURIParser.h | 99 + netwerk/base/nsNetAddr.cpp | 138 + netwerk/base/nsNetAddr.h | 30 + netwerk/base/nsNetSegmentUtils.h | 20 + netwerk/base/nsNetUtil.cpp | 3967 ++++++++++++++++++++ netwerk/base/nsNetUtil.h | 1068 ++++++ netwerk/base/nsNetworkInfoService.cpp | 94 + netwerk/base/nsNetworkInfoService.h | 40 + netwerk/base/nsPACMan.cpp | 1009 +++++ netwerk/base/nsPACMan.h | 295 ++ netwerk/base/nsPISocketTransportService.idl | 59 + netwerk/base/nsPreloadedStream.cpp | 148 + netwerk/base/nsPreloadedStream.h | 57 + netwerk/base/nsProtocolProxyService.cpp | 2458 ++++++++++++ netwerk/base/nsProtocolProxyService.h | 420 +++ netwerk/base/nsProxyInfo.cpp | 216 ++ netwerk/base/nsProxyInfo.h | 100 + netwerk/base/nsReadLine.h | 131 + netwerk/base/nsRedirectHistoryEntry.cpp | 43 + netwerk/base/nsRedirectHistoryEntry.h | 37 + netwerk/base/nsRequestObserverProxy.cpp | 175 + netwerk/base/nsRequestObserverProxy.h | 56 + netwerk/base/nsSerializationHelper.cpp | 54 + netwerk/base/nsSerializationHelper.h | 35 + netwerk/base/nsServerSocket.cpp | 582 +++ netwerk/base/nsServerSocket.h | 70 + netwerk/base/nsSimpleNestedURI.cpp | 228 ++ netwerk/base/nsSimpleNestedURI.h | 112 + netwerk/base/nsSimpleStreamListener.cpp | 69 + netwerk/base/nsSimpleStreamListener.h | 35 + netwerk/base/nsSimpleURI.cpp | 782 ++++ netwerk/base/nsSimpleURI.h | 164 + netwerk/base/nsSocketTransport2.cpp | 3404 +++++++++++++++++ netwerk/base/nsSocketTransport2.h | 485 +++ netwerk/base/nsSocketTransportService2.cpp | 1932 ++++++++++ netwerk/base/nsSocketTransportService2.h | 361 ++ netwerk/base/nsStandardURL.cpp | 3910 +++++++++++++++++++ netwerk/base/nsStandardURL.h | 626 +++ netwerk/base/nsStreamListenerTee.cpp | 159 + netwerk/base/nsStreamListenerTee.h | 46 + netwerk/base/nsStreamListenerWrapper.cpp | 39 + netwerk/base/nsStreamListenerWrapper.h | 43 + netwerk/base/nsStreamLoader.cpp | 137 + netwerk/base/nsStreamLoader.h | 58 + netwerk/base/nsStreamTransportService.cpp | 429 +++ netwerk/base/nsStreamTransportService.h | 47 + netwerk/base/nsSyncStreamListener.cpp | 169 + netwerk/base/nsSyncStreamListener.h | 43 + netwerk/base/nsTransportUtils.cpp | 133 + netwerk/base/nsTransportUtils.h | 25 + netwerk/base/nsUDPSocket.cpp | 1545 ++++++++ netwerk/base/nsUDPSocket.h | 118 + netwerk/base/nsURIHashKey.h | 71 + netwerk/base/nsURLHelper.cpp | 1167 ++++++ netwerk/base/nsURLHelper.h | 366 ++ netwerk/base/nsURLHelperOSX.cpp | 205 + netwerk/base/nsURLHelperUnix.cpp | 109 + netwerk/base/nsURLHelperWin.cpp | 111 + netwerk/base/nsURLParsers.cpp | 644 ++++ netwerk/base/nsURLParsers.h | 119 + netwerk/base/rust-helper/Cargo.toml | 10 + netwerk/base/rust-helper/cbindgen.toml | 18 + netwerk/base/rust-helper/moz.build | 12 + netwerk/base/rust-helper/src/lib.rs | 360 ++ 310 files changed, 78822 insertions(+) create mode 100644 netwerk/base/ARefBase.h create mode 100644 netwerk/base/ArrayBufferInputStream.cpp create mode 100644 netwerk/base/ArrayBufferInputStream.h create mode 100644 netwerk/base/AutoClose.h create mode 100644 netwerk/base/BackgroundFileSaver.cpp create mode 100644 netwerk/base/BackgroundFileSaver.h create mode 100644 netwerk/base/CacheInfoIPCTypes.h create mode 100644 netwerk/base/CaptivePortalService.cpp create mode 100644 netwerk/base/CaptivePortalService.h create mode 100644 netwerk/base/Dashboard.cpp create mode 100644 netwerk/base/Dashboard.h create mode 100644 netwerk/base/DashboardTypes.h create mode 100644 netwerk/base/DefaultURI.cpp create mode 100644 netwerk/base/DefaultURI.h create mode 100644 netwerk/base/EventTokenBucket.cpp create mode 100644 netwerk/base/EventTokenBucket.h create mode 100644 netwerk/base/FuzzyLayer.cpp create mode 100644 netwerk/base/FuzzyLayer.h create mode 100644 netwerk/base/FuzzySecurityInfo.cpp create mode 100644 netwerk/base/FuzzySecurityInfo.h create mode 100644 netwerk/base/FuzzySocketControl.cpp create mode 100644 netwerk/base/FuzzySocketControl.h create mode 100644 netwerk/base/IOActivityMonitor.cpp create mode 100644 netwerk/base/IOActivityMonitor.h create mode 100644 netwerk/base/IPv6Utils.h create mode 100644 netwerk/base/InterceptionInfo.cpp create mode 100644 netwerk/base/InterceptionInfo.h create mode 100644 netwerk/base/LoadContextInfo.cpp create mode 100644 netwerk/base/LoadContextInfo.h create mode 100644 netwerk/base/LoadInfo.cpp create mode 100644 netwerk/base/LoadInfo.h create mode 100644 netwerk/base/LoadTainting.h create mode 100644 netwerk/base/MemoryDownloader.cpp create mode 100644 netwerk/base/MemoryDownloader.h create mode 100644 netwerk/base/NetUtil.sys.mjs create mode 100644 netwerk/base/NetworkConnectivityService.cpp create mode 100644 netwerk/base/NetworkConnectivityService.h create mode 100644 netwerk/base/NetworkDataCountLayer.cpp create mode 100644 netwerk/base/NetworkDataCountLayer.h create mode 100644 netwerk/base/NetworkInfoServiceCocoa.cpp create mode 100644 netwerk/base/NetworkInfoServiceImpl.h create mode 100644 netwerk/base/NetworkInfoServiceLinux.cpp create mode 100644 netwerk/base/NetworkInfoServiceWindows.cpp create mode 100644 netwerk/base/PollableEvent.cpp create mode 100644 netwerk/base/PollableEvent.h create mode 100644 netwerk/base/Predictor.cpp create mode 100644 netwerk/base/Predictor.h create mode 100644 netwerk/base/PrivateBrowsingChannel.h create mode 100644 netwerk/base/ProtocolHandlerInfo.cpp create mode 100644 netwerk/base/ProtocolHandlerInfo.h create mode 100644 netwerk/base/ProxyAutoConfig.cpp create mode 100644 netwerk/base/ProxyAutoConfig.h create mode 100644 netwerk/base/ProxyConfig.h create mode 100644 netwerk/base/RedirectChannelRegistrar.cpp create mode 100644 netwerk/base/RedirectChannelRegistrar.h create mode 100644 netwerk/base/RequestContextService.cpp create mode 100644 netwerk/base/RequestContextService.h create mode 100644 netwerk/base/SSLTokensCache.cpp create mode 100644 netwerk/base/SSLTokensCache.h create mode 100644 netwerk/base/ShutdownLayer.cpp create mode 100644 netwerk/base/ShutdownLayer.h create mode 100644 netwerk/base/SimpleBuffer.cpp create mode 100644 netwerk/base/SimpleBuffer.h create mode 100644 netwerk/base/SimpleChannel.cpp create mode 100644 netwerk/base/SimpleChannel.h create mode 100644 netwerk/base/SimpleChannelParent.cpp create mode 100644 netwerk/base/SimpleChannelParent.h create mode 100644 netwerk/base/TLSServerSocket.cpp create mode 100644 netwerk/base/TLSServerSocket.h create mode 100644 netwerk/base/TRRLoadInfo.cpp create mode 100644 netwerk/base/TRRLoadInfo.h create mode 100644 netwerk/base/ThrottleQueue.cpp create mode 100644 netwerk/base/ThrottleQueue.h create mode 100644 netwerk/base/Tickler.cpp create mode 100644 netwerk/base/Tickler.h create mode 100644 netwerk/base/ascii_pac_utils.js create mode 100644 netwerk/base/http-sfv/Cargo.toml create mode 100644 netwerk/base/http-sfv/SFVService.cpp create mode 100644 netwerk/base/http-sfv/SFVService.h create mode 100644 netwerk/base/http-sfv/moz.build create mode 100644 netwerk/base/http-sfv/nsIStructuredFieldValues.idl create mode 100644 netwerk/base/http-sfv/src/lib.rs create mode 100644 netwerk/base/makecppstring.py create mode 100644 netwerk/base/moz.build create mode 100644 netwerk/base/mozIThirdPartyUtil.idl create mode 100644 netwerk/base/mozurl/.gitignore create mode 100644 netwerk/base/mozurl/Cargo.toml create mode 100644 netwerk/base/mozurl/MozURL.cpp create mode 100644 netwerk/base/mozurl/MozURL.h create mode 100644 netwerk/base/mozurl/cbindgen.toml create mode 100644 netwerk/base/mozurl/moz.build create mode 100644 netwerk/base/mozurl/src/lib.rs create mode 100644 netwerk/base/netCore.h create mode 100644 netwerk/base/nsASocketHandler.h create mode 100644 netwerk/base/nsAsyncRedirectVerifyHelper.cpp create mode 100644 netwerk/base/nsAsyncRedirectVerifyHelper.h create mode 100644 netwerk/base/nsAsyncStreamCopier.cpp create mode 100644 netwerk/base/nsAsyncStreamCopier.h create mode 100644 netwerk/base/nsAuthInformationHolder.cpp create mode 100644 netwerk/base/nsAuthInformationHolder.h create mode 100644 netwerk/base/nsBase64Encoder.cpp create mode 100644 netwerk/base/nsBase64Encoder.h create mode 100644 netwerk/base/nsBaseChannel.cpp create mode 100644 netwerk/base/nsBaseChannel.h create mode 100644 netwerk/base/nsBaseContentStream.cpp create mode 100644 netwerk/base/nsBaseContentStream.h create mode 100644 netwerk/base/nsBufferedStreams.cpp create mode 100644 netwerk/base/nsBufferedStreams.h create mode 100644 netwerk/base/nsDNSPrefetch.cpp create mode 100644 netwerk/base/nsDNSPrefetch.h create mode 100644 netwerk/base/nsDirectoryIndexStream.cpp create mode 100644 netwerk/base/nsDirectoryIndexStream.h create mode 100644 netwerk/base/nsDownloader.cpp create mode 100644 netwerk/base/nsDownloader.h create mode 100644 netwerk/base/nsFileStreams.cpp create mode 100644 netwerk/base/nsFileStreams.h create mode 100644 netwerk/base/nsIArrayBufferInputStream.idl create mode 100644 netwerk/base/nsIAsyncStreamCopier.idl create mode 100644 netwerk/base/nsIAsyncStreamCopier2.idl create mode 100644 netwerk/base/nsIAsyncVerifyRedirectCallback.idl create mode 100644 netwerk/base/nsIAuthInformation.idl create mode 100644 netwerk/base/nsIAuthModule.idl create mode 100644 netwerk/base/nsIAuthPrompt.idl create mode 100644 netwerk/base/nsIAuthPrompt2.idl create mode 100644 netwerk/base/nsIAuthPromptAdapterFactory.idl create mode 100644 netwerk/base/nsIAuthPromptCallback.idl create mode 100644 netwerk/base/nsIAuthPromptProvider.idl create mode 100644 netwerk/base/nsIBackgroundFileSaver.idl create mode 100644 netwerk/base/nsIBufferedStreams.idl create mode 100644 netwerk/base/nsIByteRangeRequest.idl create mode 100644 netwerk/base/nsICacheInfoChannel.idl create mode 100644 netwerk/base/nsICachingChannel.idl create mode 100644 netwerk/base/nsICancelable.idl create mode 100644 netwerk/base/nsICaptivePortalService.idl create mode 100644 netwerk/base/nsIChannel.idl create mode 100644 netwerk/base/nsIChannelEventSink.idl create mode 100644 netwerk/base/nsIChildChannel.idl create mode 100644 netwerk/base/nsIClassOfService.idl create mode 100644 netwerk/base/nsIClassifiedChannel.idl create mode 100644 netwerk/base/nsIContentSniffer.idl create mode 100644 netwerk/base/nsIDHCPClient.idl create mode 100644 netwerk/base/nsIDashboard.idl create mode 100644 netwerk/base/nsIDashboardEventNotifier.idl create mode 100644 netwerk/base/nsIDownloader.idl create mode 100644 netwerk/base/nsIEncodedChannel.idl create mode 100644 netwerk/base/nsIExternalProtocolHandler.idl create mode 100644 netwerk/base/nsIFileStreams.idl create mode 100644 netwerk/base/nsIFileURL.idl create mode 100644 netwerk/base/nsIForcePendingChannel.idl create mode 100644 netwerk/base/nsIFormPOSTActionChannel.idl create mode 100644 netwerk/base/nsIHttpAuthenticatorCallback.idl create mode 100644 netwerk/base/nsIHttpPushListener.idl create mode 100644 netwerk/base/nsIIOService.idl create mode 100644 netwerk/base/nsIIncrementalDownload.idl create mode 100644 netwerk/base/nsIIncrementalStreamLoader.idl create mode 100644 netwerk/base/nsIInputStreamChannel.idl create mode 100644 netwerk/base/nsIInputStreamPump.idl create mode 100644 netwerk/base/nsIInterceptionInfo.idl create mode 100644 netwerk/base/nsILoadContextInfo.idl create mode 100644 netwerk/base/nsILoadGroup.idl create mode 100644 netwerk/base/nsILoadGroupChild.idl create mode 100644 netwerk/base/nsILoadInfo.idl create mode 100644 netwerk/base/nsIMIMEInputStream.idl create mode 100644 netwerk/base/nsIMultiPartChannel.idl create mode 100644 netwerk/base/nsINestedURI.idl create mode 100644 netwerk/base/nsINetAddr.idl create mode 100644 netwerk/base/nsINetUtil.idl create mode 100644 netwerk/base/nsINetworkConnectivityService.idl create mode 100644 netwerk/base/nsINetworkInfoService.idl create mode 100644 netwerk/base/nsINetworkInterceptController.idl create mode 100644 netwerk/base/nsINetworkLinkService.idl create mode 100644 netwerk/base/nsINetworkPredictor.idl create mode 100644 netwerk/base/nsINetworkPredictorVerifier.idl create mode 100644 netwerk/base/nsINullChannel.idl create mode 100644 netwerk/base/nsIOService.cpp create mode 100644 netwerk/base/nsIOService.h create mode 100644 netwerk/base/nsIParentChannel.idl create mode 100644 netwerk/base/nsIParentRedirectingChannel.idl create mode 100644 netwerk/base/nsIPermission.idl create mode 100644 netwerk/base/nsIPermissionManager.idl create mode 100644 netwerk/base/nsIPrivateBrowsingChannel.idl create mode 100644 netwerk/base/nsIProgressEventSink.idl create mode 100644 netwerk/base/nsIPrompt.idl create mode 100644 netwerk/base/nsIProtocolHandler.idl create mode 100644 netwerk/base/nsIProtocolProxyCallback.idl create mode 100644 netwerk/base/nsIProtocolProxyFilter.idl create mode 100644 netwerk/base/nsIProtocolProxyService.idl create mode 100644 netwerk/base/nsIProtocolProxyService2.idl create mode 100644 netwerk/base/nsIProxiedChannel.idl create mode 100644 netwerk/base/nsIProxiedProtocolHandler.idl create mode 100644 netwerk/base/nsIProxyInfo.idl create mode 100644 netwerk/base/nsIRandomGenerator.idl create mode 100644 netwerk/base/nsIRedirectChannelRegistrar.idl create mode 100644 netwerk/base/nsIRedirectHistoryEntry.idl create mode 100644 netwerk/base/nsIRedirectResultListener.idl create mode 100644 netwerk/base/nsIRequest.idl create mode 100644 netwerk/base/nsIRequestContext.idl create mode 100644 netwerk/base/nsIRequestObserver.idl create mode 100644 netwerk/base/nsIRequestObserverProxy.idl create mode 100644 netwerk/base/nsIResumableChannel.idl create mode 100644 netwerk/base/nsISecCheckWrapChannel.idl create mode 100644 netwerk/base/nsISecureBrowserUI.idl create mode 100644 netwerk/base/nsISensitiveInfoHiddenURI.idl create mode 100644 netwerk/base/nsISerializationHelper.idl create mode 100644 netwerk/base/nsIServerSocket.idl create mode 100644 netwerk/base/nsISimpleStreamListener.idl create mode 100644 netwerk/base/nsISimpleURIMutator.idl create mode 100644 netwerk/base/nsISocketFilter.idl create mode 100644 netwerk/base/nsISocketTransport.idl create mode 100644 netwerk/base/nsISocketTransportService.idl create mode 100644 netwerk/base/nsISpeculativeConnect.idl create mode 100644 netwerk/base/nsIStandardURL.idl create mode 100644 netwerk/base/nsIStreamListener.idl create mode 100644 netwerk/base/nsIStreamListenerTee.idl create mode 100644 netwerk/base/nsIStreamLoader.idl create mode 100644 netwerk/base/nsIStreamTransportService.idl create mode 100644 netwerk/base/nsISyncStreamListener.idl create mode 100644 netwerk/base/nsISystemProxySettings.idl create mode 100644 netwerk/base/nsITLSServerSocket.idl create mode 100644 netwerk/base/nsIThreadRetargetableRequest.idl create mode 100644 netwerk/base/nsIThreadRetargetableStreamListener.idl create mode 100644 netwerk/base/nsIThrottledInputChannel.idl create mode 100644 netwerk/base/nsITimedChannel.idl create mode 100644 netwerk/base/nsITraceableChannel.idl create mode 100644 netwerk/base/nsITransport.idl create mode 100644 netwerk/base/nsIUDPSocket.idl create mode 100644 netwerk/base/nsIURI.idl create mode 100644 netwerk/base/nsIURIMutator.idl create mode 100644 netwerk/base/nsIURIMutatorUtils.cpp create mode 100644 netwerk/base/nsIURIWithSpecialOrigin.idl create mode 100644 netwerk/base/nsIURL.idl create mode 100644 netwerk/base/nsIURLParser.idl create mode 100644 netwerk/base/nsIUploadChannel.idl create mode 100644 netwerk/base/nsIUploadChannel2.idl create mode 100644 netwerk/base/nsIncrementalDownload.cpp create mode 100644 netwerk/base/nsIncrementalStreamLoader.cpp create mode 100644 netwerk/base/nsIncrementalStreamLoader.h create mode 100644 netwerk/base/nsInputStreamChannel.cpp create mode 100644 netwerk/base/nsInputStreamChannel.h create mode 100644 netwerk/base/nsInputStreamPump.cpp create mode 100644 netwerk/base/nsInputStreamPump.h create mode 100644 netwerk/base/nsLoadGroup.cpp create mode 100644 netwerk/base/nsLoadGroup.h create mode 100644 netwerk/base/nsMIMEInputStream.cpp create mode 100644 netwerk/base/nsMIMEInputStream.h create mode 100644 netwerk/base/nsMediaFragmentURIParser.cpp create mode 100644 netwerk/base/nsMediaFragmentURIParser.h create mode 100644 netwerk/base/nsNetAddr.cpp create mode 100644 netwerk/base/nsNetAddr.h create mode 100644 netwerk/base/nsNetSegmentUtils.h create mode 100644 netwerk/base/nsNetUtil.cpp create mode 100644 netwerk/base/nsNetUtil.h create mode 100644 netwerk/base/nsNetworkInfoService.cpp create mode 100644 netwerk/base/nsNetworkInfoService.h create mode 100644 netwerk/base/nsPACMan.cpp create mode 100644 netwerk/base/nsPACMan.h create mode 100644 netwerk/base/nsPISocketTransportService.idl create mode 100644 netwerk/base/nsPreloadedStream.cpp create mode 100644 netwerk/base/nsPreloadedStream.h create mode 100644 netwerk/base/nsProtocolProxyService.cpp create mode 100644 netwerk/base/nsProtocolProxyService.h create mode 100644 netwerk/base/nsProxyInfo.cpp create mode 100644 netwerk/base/nsProxyInfo.h create mode 100644 netwerk/base/nsReadLine.h create mode 100644 netwerk/base/nsRedirectHistoryEntry.cpp create mode 100644 netwerk/base/nsRedirectHistoryEntry.h create mode 100644 netwerk/base/nsRequestObserverProxy.cpp create mode 100644 netwerk/base/nsRequestObserverProxy.h create mode 100644 netwerk/base/nsSerializationHelper.cpp create mode 100644 netwerk/base/nsSerializationHelper.h create mode 100644 netwerk/base/nsServerSocket.cpp create mode 100644 netwerk/base/nsServerSocket.h create mode 100644 netwerk/base/nsSimpleNestedURI.cpp create mode 100644 netwerk/base/nsSimpleNestedURI.h create mode 100644 netwerk/base/nsSimpleStreamListener.cpp create mode 100644 netwerk/base/nsSimpleStreamListener.h create mode 100644 netwerk/base/nsSimpleURI.cpp create mode 100644 netwerk/base/nsSimpleURI.h create mode 100644 netwerk/base/nsSocketTransport2.cpp create mode 100644 netwerk/base/nsSocketTransport2.h create mode 100644 netwerk/base/nsSocketTransportService2.cpp create mode 100644 netwerk/base/nsSocketTransportService2.h create mode 100644 netwerk/base/nsStandardURL.cpp create mode 100644 netwerk/base/nsStandardURL.h create mode 100644 netwerk/base/nsStreamListenerTee.cpp create mode 100644 netwerk/base/nsStreamListenerTee.h create mode 100644 netwerk/base/nsStreamListenerWrapper.cpp create mode 100644 netwerk/base/nsStreamListenerWrapper.h create mode 100644 netwerk/base/nsStreamLoader.cpp create mode 100644 netwerk/base/nsStreamLoader.h create mode 100644 netwerk/base/nsStreamTransportService.cpp create mode 100644 netwerk/base/nsStreamTransportService.h create mode 100644 netwerk/base/nsSyncStreamListener.cpp create mode 100644 netwerk/base/nsSyncStreamListener.h create mode 100644 netwerk/base/nsTransportUtils.cpp create mode 100644 netwerk/base/nsTransportUtils.h create mode 100644 netwerk/base/nsUDPSocket.cpp create mode 100644 netwerk/base/nsUDPSocket.h create mode 100644 netwerk/base/nsURIHashKey.h create mode 100644 netwerk/base/nsURLHelper.cpp create mode 100644 netwerk/base/nsURLHelper.h create mode 100644 netwerk/base/nsURLHelperOSX.cpp create mode 100644 netwerk/base/nsURLHelperUnix.cpp create mode 100644 netwerk/base/nsURLHelperWin.cpp create mode 100644 netwerk/base/nsURLParsers.cpp create mode 100644 netwerk/base/nsURLParsers.h create mode 100644 netwerk/base/rust-helper/Cargo.toml create mode 100644 netwerk/base/rust-helper/cbindgen.toml create mode 100644 netwerk/base/rust-helper/moz.build create mode 100644 netwerk/base/rust-helper/src/lib.rs (limited to 'netwerk/base') diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h new file mode 100644 index 0000000000..207b5fe03c --- /dev/null +++ b/netwerk/base/ARefBase.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ARefBase_h +#define mozilla_net_ARefBase_h + +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace net { + +// This is an abstract class that can be pointed to by either +// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic +// objects that need to be reference counted - similiar to nsISupports +// but it may or may not be xpcom. + +class ARefBase { + public: + ARefBase() = default; + virtual ~ARefBase() = default; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp new file mode 100644 index 0000000000..cd4f951f8a --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.cpp @@ -0,0 +1,132 @@ +/* -*- 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 +#include "ArrayBufferInputStream.h" +#include "nsStreamUtils.h" +#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject} +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/Value.h" // JS::Value +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/ScriptSettings.h" + +using mozilla::dom::RootingCx; + +NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, + nsIInputStream); + +NS_IMETHODIMP +ArrayBufferInputStream::SetData(JS::Handle aBuffer, + uint64_t aByteOffset, uint64_t aLength) { + NS_ASSERT_OWNINGTHREAD(ArrayBufferInputStream); + + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::Rooted arrayBuffer(RootingCx(), &aBuffer.toObject()); + if (!JS::IsArrayBufferObject(arrayBuffer)) { + return NS_ERROR_FAILURE; + } + + uint64_t buflen = JS::GetArrayBufferByteLength(arrayBuffer); + uint64_t offset = std::min(buflen, aByteOffset); + uint64_t bufferLength = std::min(buflen - offset, aLength); + + // Prevent truncation. + if (bufferLength > UINT32_MAX) { + return NS_ERROR_INVALID_ARG; + } + + mArrayBuffer = mozilla::MakeUniqueFallible(bufferLength); + if (!mArrayBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mBufferLength = bufferLength; + + 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::StreamStatus() { + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t aCount, uint32_t* result) { + NS_ASSERTION(result, "null ptr"); + NS_ASSERTION(mBufferLength >= mPos, "bad stream state"); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), + "stream inited incorrectly"); + + *result = 0; + while (mPos < mBufferLength) { + uint32_t remaining = mBufferLength - mPos; + MOZ_ASSERT(mArrayBuffer); + + uint32_t count = std::min(aCount, remaining); + if (count == 0) { + break; + } + + uint32_t written; + nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, + &written); + if (NS_FAILED(rv)) { + // InputStreams do not propagate errors to caller. + return NS_OK; + } + + NS_ASSERTION(written <= count, + "writer should not write more than we asked it to write"); + mPos += written; + *result += written; + aCount -= written; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h new file mode 100644 index 0000000000..3a4811373d --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArrayBufferInputStream_h +#define ArrayBufferInputStream_h + +#include "nsIArrayBufferInputStream.h" +#include "js/Value.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" + +#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID \ + "@mozilla.org/io/arraybuffer-input-stream;1" +#define NS_ARRAYBUFFERINPUTSTREAM_CID \ + { /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \ + 0x3014dde6, 0xaa1c, 0x41db, { \ + 0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6 \ + } \ + } + +class ArrayBufferInputStream : public nsIArrayBufferInputStream { + public: + ArrayBufferInputStream() = default; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIARRAYBUFFERINPUTSTREAM + NS_DECL_NSIINPUTSTREAM + + private: + virtual ~ArrayBufferInputStream() = default; + mozilla::UniquePtr mArrayBuffer; + uint32_t mBufferLength{0}; + uint32_t mPos{0}; + bool mClosed{false}; +}; + +#endif // ArrayBufferInputStream_h diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h new file mode 100644 index 0000000000..a0e3a48e17 --- /dev/null +++ b/netwerk/base/AutoClose.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_AutoClose_h +#define mozilla_net_AutoClose_h + +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +// A container for XPCOM streams (e.g. nsIAsyncInputStream) and other +// refcounted classes that need to have the Close() method called explicitly +// before they are destroyed. +template +class AutoClose { + public: + AutoClose() : mMutex("net::AutoClose.mMutex") {} + ~AutoClose() { CloseAndRelease(); } + + explicit operator bool() { + MutexAutoLock lock(mMutex); + return mPtr; + } + + already_AddRefed forget() { + MutexAutoLock lock(mMutex); + return mPtr.forget(); + } + + void takeOver(nsCOMPtr& rhs) { TakeOverInternal(rhs.forget()); } + + void CloseAndRelease() { TakeOverInternal(nullptr); } + + private: + void TakeOverInternal(already_AddRefed&& aOther) { + nsCOMPtr ptr(std::move(aOther)); + { + MutexAutoLock lock(mMutex); + ptr.swap(mPtr); + } + + if (ptr) { + ptr->Close(); + } + } + + void operator=(const AutoClose&) = delete; + AutoClose(const AutoClose&) = delete; + + nsCOMPtr mPtr; + Mutex mMutex MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_AutoClose_h diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp new file mode 100644 index 0000000000..bc52097da9 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.cpp @@ -0,0 +1,1121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BackgroundFileSaver.h" + +#include "ScopedNSSTypes.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Casting.h" +#include "mozilla/Logging.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Telemetry.h" +#include "nsCOMArray.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentSubstring.h" +#include "nsIAsyncInputStream.h" +#include "nsIFile.h" +#include "nsIMutableArray.h" +#include "nsIPipe.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "pk11pub.h" +#include "secoidt.h" + +#ifdef XP_WIN +# include +# include +# include +#endif // XP_WIN + +namespace mozilla { +namespace net { + +// MOZ_LOG=BackgroundFileSaver:5 +static LazyLogModule prlog("BackgroundFileSaver"); +#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug) + +//////////////////////////////////////////////////////////////////////////////// +//// Globals + +/** + * Buffer size for writing to the output file or reading from the input file. + */ +#define BUFFERED_IO_SIZE (1024 * 32) + +/** + * When this upper limit is reached, the original request is suspended. + */ +#define REQUEST_SUSPEND_AT (1024 * 1024 * 4) + +/** + * When this lower limit is reached, the original request is resumed. + */ +#define REQUEST_RESUME_AT (1024 * 1024 * 2) + +//////////////////////////////////////////////////////////////////////////////// +//// NotifyTargetChangeRunnable + +/** + * Runnable object used to notify the control thread that file contents will now + * be saved to the specified file. + */ +class NotifyTargetChangeRunnable final : public Runnable { + public: + NotifyTargetChangeRunnable(BackgroundFileSaver* aSaver, nsIFile* aTarget) + : Runnable("net::NotifyTargetChangeRunnable"), + mSaver(aSaver), + mTarget(aTarget) {} + + NS_IMETHOD Run() override { return mSaver->NotifyTargetChange(mTarget); } + + private: + RefPtr mSaver; + nsCOMPtr mTarget; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +uint32_t BackgroundFileSaver::sThreadCount = 0; +uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0; + +BackgroundFileSaver::BackgroundFileSaver() { + LOG(("Created BackgroundFileSaver [this = %p]", this)); +} + +BackgroundFileSaver::~BackgroundFileSaver() { + LOG(("Destroying BackgroundFileSaver [this = %p]", this)); +} + +// Called on the control thread. +nsresult BackgroundFileSaver::Init() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + NS_NewPipe2(getter_AddRefs(mPipeInputStream), + getter_AddRefs(mPipeOutputStream), true, true, 0, + HasInfiniteBuffer() ? UINT32_MAX : 0); + + mControlEventTarget = GetCurrentSerialEventTarget(); + NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = NS_CreateBackgroundTaskQueue("BgFileSaver", + getter_AddRefs(mBackgroundET)); + NS_ENSURE_SUCCESS(rv, rv); + + sThreadCount++; + if (sThreadCount > sTelemetryMaxThreadCount) { + sTelemetryMaxThreadCount = sThreadCount; + } + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver** aObserver) { + NS_ENSURE_ARG_POINTER(aObserver); + *aObserver = do_AddRef(mObserver).take(); + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) { + mObserver = aObserver; + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::EnableAppend() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + MutexAutoLock lock(mLock); + mAppend = true; + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) { + NS_ENSURE_ARG(aTarget); + { + MutexAutoLock lock(mLock); + if (!mInitialTarget) { + aTarget->Clone(getter_AddRefs(mInitialTarget)); + mInitialTargetKeepPartial = aKeepPartial; + } else { + aTarget->Clone(getter_AddRefs(mRenamedTarget)); + mRenamedTargetKeepPartial = aKeepPartial; + } + } + + // After the worker thread wakes up because attention is requested, it will + // rename or create the target file as requested, and start copying data. + return GetWorkerThreadAttention(true); +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::Finish(nsresult aStatus) { + nsresult rv; + + // This will cause the NS_AsyncCopy operation, if it's in progress, to consume + // all the data that is still in the pipe, and then finish. + rv = mPipeOutputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + // Ensure that, when we get attention from the worker thread, if no pending + // rename operation is waiting, the operation will complete. + { + MutexAutoLock lock(mLock); + mFinishRequested = true; + if (NS_SUCCEEDED(mStatus)) { + mStatus = aStatus; + } + } + + // After the worker thread wakes up because attention is requested, it will + // process the completion conditions, detect that completion is requested, and + // notify the main thread of the completion. If this function was called with + // a success code, we wait for the copy to finish before processing the + // completion conditions, otherwise we interrupt the copy immediately. + return GetWorkerThreadAttention(NS_FAILED(aStatus)); +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSha256() { + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable sha256 or initialize NSS off the main thread"); + // Ensure Personal Security Manager is initialized. This is required for + // PK11_* operations to work. + nsresult rv; + nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + MutexAutoLock lock(mLock); + mSha256Enabled = true; // this will be read by the worker thread + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSha256Hash(nsACString& aHash) { + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread"); + // We acquire a lock because mSha256 is written on the worker thread. + MutexAutoLock lock(mLock); + if (mSha256.IsEmpty()) { + return NS_ERROR_NOT_AVAILABLE; + } + aHash = mSha256; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSignatureInfo() { + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable signature extraction off the main thread"); + // Ensure Personal Security Manager is initialized. + nsresult rv; + nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + MutexAutoLock lock(mLock); + mSignatureInfoEnabled = true; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSignatureInfo( + nsTArray>>& aSignatureInfo) { + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread"); + // We acquire a lock because mSignatureInfo is written on the worker thread. + MutexAutoLock lock(mLock); + if (!mComplete || !mSignatureInfoEnabled) { + return NS_ERROR_NOT_AVAILABLE; + } + for (const auto& signatureChain : mSignatureInfo) { + aSignatureInfo.AppendElement(TransformIntoNewArray( + signatureChain, [](const auto& element) { return element.Clone(); })); + } + return NS_OK; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::GetWorkerThreadAttention( + bool aShouldInterruptCopy) { + nsresult rv; + + MutexAutoLock lock(mLock); + + // We only require attention one time. If this function is called two times + // before the worker thread wakes up, and the first has aShouldInterruptCopy + // false and the second true, we won't forcibly interrupt the copy from the + // control thread. However, that never happens, because calling Finish with a + // success code is the only case that may result in aShouldInterruptCopy being + // false. In that case, we won't call this function again, because consumers + // should not invoke other methods on the control thread after calling Finish. + // And in any case, Finish already closes one end of the pipe, causing the + // copy to finish properly on its own. + if (mWorkerThreadAttentionRequested) { + return NS_OK; + } + + if (!mAsyncCopyContext) { + // Background event queues are not shutdown and could be called after + // the queue is reset to null. To match the behavior of nsIThread + // return NS_ERROR_UNEXPECTED + if (!mBackgroundET) { + return NS_ERROR_UNEXPECTED; + } + + // Copy is not in progress, post an event to handle the change manually. + rv = mBackgroundET->Dispatch( + NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this, + &BackgroundFileSaver::ProcessAttention), + NS_DISPATCH_EVENT_MAY_BLOCK); + NS_ENSURE_SUCCESS(rv, rv); + + } else if (aShouldInterruptCopy) { + // Interrupt the copy. The copy will be resumed, if needed, by the + // ProcessAttention function, invoked by the AsyncCopyCallback function. + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + } + + // Indicate that attention has been requested successfully, there is no need + // to post another event until the worker thread processes the current one. + mWorkerThreadAttentionRequested = true; + + return NS_OK; +} + +// Called on the worker thread. +// static +void BackgroundFileSaver::AsyncCopyCallback(void* aClosure, nsresult aStatus) { + // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object + // alive even if other references disappeared. At the end of this method, + // we've finished using the object and can safely release our reference. + RefPtr self = + dont_AddRef((BackgroundFileSaver*)aClosure); + { + MutexAutoLock lock(self->mLock); + + // Now that the copy was interrupted or terminated, any notification from + // the control thread requires an event to be posted to the worker thread. + self->mAsyncCopyContext = nullptr; + + // When detecting failures, ignore the status code we use to interrupt. + if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT && + NS_SUCCEEDED(self->mStatus)) { + self->mStatus = aStatus; + } + } + + (void)self->ProcessAttention(); +} + +// Called on the worker thread. +nsresult BackgroundFileSaver::ProcessAttention() { + nsresult rv; + + // This function is called whenever the attention of the worker thread has + // been requested. This may happen in these cases: + // * We are about to start the copy for the first time. In this case, we are + // called from an event posted on the worker thread from the control thread + // by GetWorkerThreadAttention, and mAsyncCopyContext is null. + // * We have interrupted the copy for some reason. In this case, we are + // called by AsyncCopyCallback, and mAsyncCopyContext is null. + // * We are currently executing ProcessStateChange, and attention is requested + // by the control thread, for example because SetTarget or Finish have been + // called. In this case, we are called from from an event posted through + // GetWorkerThreadAttention. While mAsyncCopyContext was always null when + // the event was posted, at this point mAsyncCopyContext may not be null + // anymore, because ProcessStateChange may have started the copy before the + // event that called this function was processed on the worker thread. + // If mAsyncCopyContext is not null, we interrupt the copy and re-enter + // through AsyncCopyCallback. This allows us to check if, for instance, we + // should rename the target file. We will then restart the copy if needed. + + // mAsyncCopyContext is only written on the worker thread (which we are on) + MOZ_ASSERT(!NS_IsMainThread()); + { + // Even though we're the only thread that writes this, we have to take the + // lock + MutexAutoLock lock(mLock); + if (mAsyncCopyContext) { + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + return NS_OK; + } + } + // Use the current shared state to determine the next operation to execute. + rv = ProcessStateChange(); + if (NS_FAILED(rv)) { + // If something failed while processing, terminate the operation now. + { + MutexAutoLock lock(mLock); + + if (NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + } + // Ensure we notify completion now that the operation failed. + CheckCompletion(); + } + + return NS_OK; +} + +// Called on the worker thread. +nsresult BackgroundFileSaver::ProcessStateChange() { + nsresult rv; + + // We might have been notified because the operation is complete, verify. + if (CheckCompletion()) { + return NS_OK; + } + + // Get a copy of the current shared state for the worker thread. + nsCOMPtr initialTarget; + bool initialTargetKeepPartial; + nsCOMPtr 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 renamedTargetParentDir; + rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString renamedTargetName; + rv = renamedTarget->GetLeafName(renamedTargetName); + NS_ENSURE_SUCCESS(rv, rv); + + // We must delete any existing target file before moving the current + // one. + rv = renamedTarget->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (exists) { + rv = renamedTarget->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Move the file. If this fails, we still reference the original file + // in mActualTarget, so that it is deleted if requested. If this + // succeeds, the nsIFile instance referenced by mActualTarget mutates + // and starts pointing to the new file, but we'll discard the reference. + rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We should not only update the mActualTarget with renameTarget when + // they point to the different files. + // In this way, if mActualTarget and renamedTarget point to the same file + // with different addresses, "CheckCompletion()" will return false + // forever. + } + + // Update mActualTarget with renameTarget, + // even if they point to the same file. + mActualTarget = renamedTarget; + mActualTargetKeepPartial = renamedTargetKeepPartial; + } + + // Notify if the target file name actually changed. + if (!equalToCurrent) { + // We must clone the nsIFile instance because mActualTarget is not + // immutable, it may change if the target is renamed later. + nsCOMPtr actualTargetToNotify; + rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr event = + new NotifyTargetChangeRunnable(this, actualTargetToNotify); + NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); + + rv = mControlEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isContinuation) { + // The pending rename operation might be the last task before finishing. We + // may return here only if we have already created the target file. + if (CheckCompletion()) { + return NS_OK; + } + + // Even if the operation did not complete, the pipe input stream may be + // empty and may have been closed already. We detect this case using the + // Available property, because it never returns an error if there is more + // data to be consumed. If the pipe input stream is closed, we just exit + // and wait for more calls like SetTarget or Finish to be invoked on the + // control thread. However, we still truncate the file or create the + // initial digest context if we are expected to do that. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_FAILED(rv)) { + return NS_OK; + } + } + + // Create the digest if requested and NSS hasn't been shut down. + if (sha256Enabled && mDigest.isNothing()) { + mDigest.emplace(Digest()); + mDigest->Begin(SEC_OID_SHA256); + } + + // When we are requested to append to an existing file, we should read the + // existing data and ensure we include it as part of the final hash. + if (mDigest.isSome() && append && !isContinuation) { + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (rv != NS_ERROR_FILE_NOT_FOUND) { + NS_ENSURE_SUCCESS(rv, rv); + + // Try to clean up the inputStream if an error occurs. + auto closeGuard = + mozilla::MakeScopeExit([&] { Unused << inputStream->Close(); }); + + char buffer[BUFFERED_IO_SIZE]; + while (true) { + uint32_t count; + rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count == 0) { + // We reached the end of the file. + break; + } + + rv = mDigest->Update(BitwiseCast(buffer), count); + NS_ENSURE_SUCCESS(rv, rv); + + // The pending resume operation may have been cancelled by the control + // thread while the worker thread was reading in the existing file. + // Abort reading in the original file in that case, as the digest will + // be discarded anyway. + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return NS_ERROR_ABORT; + } + } + + // Close explicitly to handle any errors. + closeGuard.release(); + rv = inputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // We will append to the initial target file only if it was requested by the + // caller, but we'll always append on subsequent accesses to the target file. + int32_t creationIoFlags; + if (isContinuation) { + creationIoFlags = PR_APPEND; + } else { + creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; + } + + // Create the target file, or append to it if we already started writing it. + // The 0600 permissions are used while the file is being downloaded, and for + // interrupted downloads. Those may be located in the system temporary + // directory, as well as the target directory, and generally have a ".part" + // extension. Those part files should never be group or world-writable even + // if the umask allows it. + nsCOMPtr outputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget, + PR_WRONLY | creationIoFlags, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr bufferedStream; + rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream), + outputStream.forget(), BUFFERED_IO_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + outputStream = bufferedStream; + + // Wrap the output stream so that it feeds the digest if needed. + if (mDigest.isSome()) { + // Constructing the DigestOutputStream cannot fail. Passing mDigest + // to DigestOutputStream is safe, because BackgroundFileSaver always + // outlives the outputStream. BackgroundFileSaver is reference-counted + // before the call to AsyncCopy, and mDigest is never destroyed + // before AsyncCopyCallback. + outputStream = new DigestOutputStream(outputStream, mDigest.ref()); + } + + // Start copying our input to the target file. No errors can be raised past + // this point if the copy starts, since they should be handled by the thread. + { + MutexAutoLock lock(mLock); + + rv = NS_AsyncCopy(mPipeInputStream, outputStream, mBackgroundET, + NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, + this, false, true, getter_AddRefs(mAsyncCopyContext), + GetProgressCallback()); + if (NS_FAILED(rv)) { + NS_WARNING("NS_AsyncCopy failed."); + mAsyncCopyContext = nullptr; + return rv; + } + } + + // If the operation succeeded, we must ensure that we keep this object alive + // for the entire duration of the copy, since only the raw pointer will be + // provided as the argument of the AsyncCopyCallback function. We can add the + // reference now, after NS_AsyncCopy returned, because it always starts + // processing asynchronously, and there is no risk that the callback is + // invoked before we reach this point. If the operation failed instead, then + // AsyncCopyCallback will never be called. + NS_ADDREF_THIS(); + + return NS_OK; +} + +// Called on the worker thread. +bool BackgroundFileSaver::CheckCompletion() { + nsresult rv; + + bool failed = true; + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(!mAsyncCopyContext, + "Should not be copying when checking completion conditions."); + + if (mComplete) { + return true; + } + + // If an error occurred, we don't need to do the checks in this code block, + // and the operation can be completed immediately with a failure code. + if (NS_SUCCEEDED(mStatus)) { + failed = false; + + // We did not incur in an error, so we must determine if we can stop now. + // If the Finish method has not been called, we can just continue now. + if (!mFinishRequested) { + return false; + } + + // We can only stop when all the operations requested by the control + // thread have been processed. First, we check whether we have processed + // the first SetTarget call, if any. Then, we check whether we have + // processed any rename requested by subsequent SetTarget calls. + if ((mInitialTarget && !mActualTarget) || + (mRenamedTarget && mRenamedTarget != mActualTarget)) { + return false; + } + + // If we still have data to write to the output file, allow the copy + // operation to resume. The Available getter may return an error if one + // of the pipe's streams has been already closed. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available != 0) { + return false; + } + } + + mComplete = true; + } + + // Ensure we notify completion now that the operation finished. + // Do a best-effort attempt to remove the file if required. + if (failed && mActualTarget && !mActualTargetKeepPartial) { + (void)mActualTarget->Remove(false); + } + + // Finish computing the hash + if (!failed && mDigest.isSome()) { + nsTArray outArray; + rv = mDigest->End(outArray); + if (NS_SUCCEEDED(rv)) { + MutexAutoLock lock(mLock); + mSha256 = nsDependentCSubstring( + BitwiseCast(outArray.Elements()), outArray.Length()); + } + } + + // Compute the signature of the binary. ExtractSignatureInfo doesn't do + // anything on non-Windows platforms except return an empty nsIArray. + if (!failed && mActualTarget) { + nsString filePath; + mActualTarget->GetTarget(filePath); + nsresult rv = ExtractSignatureInfo(filePath); + if (NS_FAILED(rv)) { + LOG(("Unable to extract signature information [this = %p].", this)); + } else { + LOG(("Signature extraction success! [this = %p]", this)); + } + } + + // Post an event to notify that the operation completed. + if (NS_FAILED(mControlEventTarget->Dispatch( + NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this, + &BackgroundFileSaver::NotifySaveComplete), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post completion event to the control thread."); + } + + return true; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::NotifyTargetChange(nsIFile* aTarget) { + if (mObserver) { + (void)mObserver->OnTargetChange(this, aTarget); + } + + return NS_OK; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::NotifySaveComplete() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + nsresult status; + { + MutexAutoLock lock(mLock); + status = mStatus; + } + + if (mObserver) { + (void)mObserver->OnSaveComplete(this, status); + // If mObserver keeps alive an enclosure that captures `this`, we'll have a + // cycle that won't be caught by the cycle-collector, so we need to break it + // when we're done here (see bug 1444265). + mObserver = nullptr; + } + + // At this point, the worker thread will not process any more events, and we + // can shut it down. Shutting down a thread may re-enter the event loop on + // this thread. This is not a problem in this case, since this function is + // called by a top-level event itself, and we have already invoked the + // completion observer callback. Re-entering the loop can only delay the + // final release and destruction of this saver object, since we are keeping a + // reference to it through the event object. + mBackgroundET = nullptr; + + sThreadCount--; + + // When there are no more active downloads, we consider the download session + // finished. We record the maximum number of concurrent downloads reached + // during the session in a telemetry histogram, and we reset the maximum + // thread counter for the next download session + if (sThreadCount == 0) { + Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT, + sTelemetryMaxThreadCount); + sTelemetryMaxThreadCount = 0; + } + + return NS_OK; +} + +nsresult BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) { + MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread"); + { + MutexAutoLock lock(mLock); + if (!mSignatureInfoEnabled) { + return NS_OK; + } + } +#ifdef XP_WIN + // Setup the file to check. + WINTRUST_FILE_INFO fileToCheck = {0}; + fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileToCheck.pcwszFilePath = filePath.Data(); + fileToCheck.hFile = nullptr; + fileToCheck.pgKnownSubject = nullptr; + + // We want to check it is signed and trusted. + WINTRUST_DATA trustData = {0}; + trustData.cbStruct = sizeof(trustData); + trustData.pPolicyCallbackData = nullptr; + trustData.pSIPClientData = nullptr; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.hWVTStateData = nullptr; + trustData.pwszURLReference = nullptr; + // Disallow revocation checks over the network + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + // no UI + trustData.dwUIContext = 0; + trustData.pFile = &fileToCheck; + + // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate + // chains up to a trusted root CA and has appropriate permissions to sign + // code. + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + // Check if the file is signed by something that is trusted. If the file is + // not signed, this is a no-op. + LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); + CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr; + // According to the Windows documentation, we should check against 0 instead + // of ERROR_SUCCESS, which is an HRESULT. + if (ret == 0) { + cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData); + } + if (cryptoProviderData) { + // Lock because signature information is read on the main thread. + MutexAutoLock lock(mLock); + LOG(("Downloaded trusted and signed file [this = %p].", this)); + // A binary may have multiple signers. Each signer may have multiple certs + // in the chain. + for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) { + const CERT_CHAIN_CONTEXT* certChainContext = + cryptoProviderData->pasSigners[i].pChainContext; + if (!certChainContext) { + break; + } + for (DWORD j = 0; j < certChainContext->cChain; ++j) { + const CERT_SIMPLE_CHAIN* certSimpleChain = + certChainContext->rgpChain[j]; + if (!certSimpleChain) { + break; + } + + nsTArray> certList; + bool extractionSuccess = true; + for (DWORD k = 0; k < certSimpleChain->cElement; ++k) { + CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k]; + if (certChainElement->pCertContext->dwCertEncodingType != + X509_ASN_ENCODING) { + continue; + } + nsTArray cert; + cert.AppendElements(certChainElement->pCertContext->pbCertEncoded, + certChainElement->pCertContext->cbCertEncoded); + certList.AppendElement(std::move(cert)); + } + if (extractionSuccess) { + mSignatureInfo.AppendElement(std::move(certList)); + } + } + } + // Free the provider data if cryptoProviderData is not null. + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(nullptr, &policyGUID, &trustData); + } else { + LOG(("Downloaded unsigned or untrusted file [this = %p].", this)); + } +#endif + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, nsIBackgroundFileSaver, + nsIOutputStream, nsIAsyncOutputStream, + nsIOutputStreamCallback) + +BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream() + : BackgroundFileSaver(), mAsyncWaitCallback(nullptr) {} + +bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; } + +nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() { + return nullptr; +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); } + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); } + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::StreamStatus() { + return mPipeOutputStream->StreamStatus(); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return mPipeOutputStream->Write(aBuf, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, uint32_t* _retval) { + return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) { + return mPipeOutputStream->IsNonBlocking(_retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) { + return mPipeOutputStream->CloseWithStatus(reason); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(!mAsyncWaitCallback); + + mAsyncWaitCallback = aCallback; + + return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount, + aEventTarget); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::OnOutputStreamReady( + nsIAsyncOutputStream* aStream) { + NS_ENSURE_STATE(mAsyncWaitCallback); + + nsCOMPtr asyncWaitCallback = nullptr; + asyncWaitCallback.swap(mAsyncWaitCallback); + + return asyncWaitCallback->OnOutputStreamReady(this); +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener + +NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver, + nsIRequestObserver, nsIStreamListener) + +bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; } + +nsAsyncCopyProgressFun +BackgroundFileSaverStreamListener::GetProgressCallback() { + return AsyncCopyProgressCallback; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest* aRequest) { + NS_ENSURE_ARG(aRequest); + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + // If an error occurred, cancel the operation immediately. On success, wait + // until the caller has determined whether the file should be renamed. + if (NS_FAILED(aStatusCode)) { + Finish(aStatusCode); + } + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) { + nsresult rv; + + NS_ENSURE_ARG(aRequest); + + // Read the requested data. Since the pipe has an infinite buffer, we don't + // expect any write error to occur here. + uint32_t writeCount; + rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + + // If reading from the input stream fails for any reason, the pipe will return + // a success code, but without reading all the data. Since we should be able + // to read the requested data when OnDataAvailable is called, raise an error. + if (writeCount < aCount) { + NS_WARNING("Reading from the input stream should not have failed."); + return NS_ERROR_UNEXPECTED; + } + + bool stateChanged = false; + { + MutexAutoLock lock(mSuspensionLock); + + if (!mReceivedTooMuchData) { + uint64_t available; + nsresult rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) { + mReceivedTooMuchData = true; + mRequest = aRequest; + stateChanged = true; + } + } + } + + if (stateChanged) { + NotifySuspendOrResume(); + } + + return NS_OK; +} + +// Called on the worker thread. +// static +void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback( + void* aClosure, uint32_t aCount) { + BackgroundFileSaverStreamListener* self = + (BackgroundFileSaverStreamListener*)aClosure; + + // Wait if the control thread is in the process of suspending or resuming. + MutexAutoLock lock(self->mSuspensionLock); + + // This function is called when some bytes are consumed by NS_AsyncCopy. Each + // time this happens, verify if a suspended request should be resumed, because + // we have now consumed enough data. + if (self->mReceivedTooMuchData) { + uint64_t available; + nsresult rv = self->mPipeInputStream->Available(&available); + if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) { + self->mReceivedTooMuchData = false; + + // Post an event to verify if the request should be resumed. + if (NS_FAILED(self->mControlEventTarget->Dispatch( + NewRunnableMethod( + "BackgroundFileSaverStreamListener::NotifySuspendOrResume", + self, + &BackgroundFileSaverStreamListener::NotifySuspendOrResume), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post resume event to the control thread."); + } + } + } +} + +// Called on the control thread. +nsresult BackgroundFileSaverStreamListener::NotifySuspendOrResume() { + // Prevent the worker thread from changing state while processing. + MutexAutoLock lock(mSuspensionLock); + + if (mReceivedTooMuchData) { + if (!mRequestSuspended) { + // Try to suspend the request. If this fails, don't try to resume later. + if (NS_SUCCEEDED(mRequest->Suspend())) { + mRequestSuspended = true; + } else { + NS_WARNING("Unable to suspend the request."); + } + } + } else { + if (mRequestSuspended) { + // Resume the request only if we succeeded in suspending it. + if (NS_SUCCEEDED(mRequest->Resume())) { + mRequestSuspended = false; + } else { + NS_WARNING("Unable to resume the request."); + } + } + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// DigestOutputStream +NS_IMPL_ISUPPORTS(DigestOutputStream, nsIOutputStream) + +DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream, + Digest& aDigest) + : mOutputStream(aStream), mDigest(aDigest) { + MOZ_ASSERT(mOutputStream, "Can't have null output stream"); +} + +NS_IMETHODIMP +DigestOutputStream::Close() { return mOutputStream->Close(); } + +NS_IMETHODIMP +DigestOutputStream::Flush() { return mOutputStream->Flush(); } + +NS_IMETHODIMP +DigestOutputStream::StreamStatus() { return mOutputStream->StreamStatus(); } + +NS_IMETHODIMP +DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) { + nsresult rv = mDigest.Update( + BitwiseCast(aBuf), aCount); + NS_ENSURE_SUCCESS(rv, rv); + + return mOutputStream->Write(aBuf, aCount, retval); +} + +NS_IMETHODIMP +DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* retval) { + // Not supported. We could read the stream to a buf, call DigestOp on the + // result, seek back and pass the stream on, but it's not worth it since our + // application (NS_AsyncCopy) doesn't invoke this on the sink. + MOZ_CRASH("DigestOutputStream::WriteFrom not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* retval) { + MOZ_CRASH("DigestOutputStream::WriteSegments not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::IsNonBlocking(bool* retval) { + return mOutputStream->IsNonBlocking(retval); +} + +#undef LOG_ENABLED + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h new file mode 100644 index 0000000000..214aa31d10 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.h @@ -0,0 +1,397 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file defines two implementations of the nsIBackgroundFileSaver + * interface. See the "test_backgroundfilesaver.js" file for usage examples. + */ + +#ifndef BackgroundFileSaver_h__ +#define BackgroundFileSaver_h__ + +#include "ScopedNSSTypes.h" +#include "mozilla/Mutex.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBackgroundFileSaver.h" +#include "nsIStreamListener.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +class nsIAsyncInputStream; +class nsISerialEventTarget; + +namespace mozilla { +namespace net { + +class DigestOutputStream; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +class BackgroundFileSaver : public nsIBackgroundFileSaver { + public: + NS_DECL_NSIBACKGROUNDFILESAVER + + BackgroundFileSaver(); + + /** + * Initializes the pipe and the worker thread on XPCOM construction. + * + * This is called automatically by the XPCOM infrastructure, and if this + * fails, the factory will delete this object without returning a reference. + */ + nsresult Init(); + + /** + * Number of worker threads that are currently running. + */ + static uint32_t sThreadCount; + + /** + * Maximum number of worker threads reached during the current download + * session, used for telemetry. + * + * When there are no more worker threads running, we consider the download + * session finished, and this counter is reset. + */ + static uint32_t sTelemetryMaxThreadCount; + + protected: + virtual ~BackgroundFileSaver(); + + /** + * Thread that constructed this object. + */ + nsCOMPtr mControlEventTarget; + + /** + * Thread to which the actual input/output is delegated. + */ + nsCOMPtr mBackgroundET; + + /** + * Stream that receives data from derived classes. The received data will be + * available to the worker thread through mPipeInputStream. This is an + * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. + */ + nsCOMPtr 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 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 mObserver; + + ////////////////////////////////////////////////////////////////////////////// + //// Shared state between control and worker threads + + /** + * Protects the shared state between control and worker threads. This mutex + * is always locked for a very short time, never during input/output. + */ + mozilla::Mutex mLock{"BackgroundFileSaver.mLock"}; + + /** + * True if the worker thread is already waiting to process a change in state. + */ + bool mWorkerThreadAttentionRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation should finish as soon as possibile. + */ + bool mFinishRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation completed, with either success or failure. + */ + bool mComplete MOZ_GUARDED_BY(mLock){false}; + + /** + * Holds the current file saver status. This is a success status while the + * object is working correctly, and remains such if the operation completes + * successfully. This becomes an error status when an error occurs on the + * worker thread, or when the operation is canceled. + */ + nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK}; + + /** + * True if we should append data to the initial target file, instead of + * overwriting it. + */ + bool mAppend MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by the first SetTarget call on the control thread, and contains + * the target file name that will be used by the worker thread, as soon as it + * is possible to update mActualTarget and open the file. This is null if no + * target was ever assigned to this object. + */ + nsCOMPtr mInitialTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by the first SetTarget call on the control thread, and + * indicates whether mInitialTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mInitialTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * contains the new target file name to which the worker thread will move the + * target file, as soon as it can be done. This is null if SetTarget was + * called only once, or no target was ever assigned to this object. + * + * The target file can be renamed multiple times, though only the most recent + * rename is guaranteed to be processed by the worker thread. + */ + nsCOMPtr mRenamedTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * indicates whether mRenamedTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mRenamedTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. + * This is read by both threads but only written by the worker thread. + */ + nsCOMPtr mAsyncCopyContext MOZ_GUARDED_BY(mLock); + + /** + * The SHA 256 hash in raw bytes of the downloaded file. This is written + * by the worker thread but can be read on the main thread. + */ + nsCString mSha256 MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to compute the hash. Must be set on the main thread before + * setTarget is called. + */ + bool mSha256Enabled MOZ_GUARDED_BY(mLock){false}; + + /** + * Store the signature info. + */ + nsTArray>> mSignatureInfo MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to extract the signature. Must be set on the main thread + * before setTarget is called. + */ + bool mSignatureInfoEnabled MOZ_GUARDED_BY(mLock){false}; + + ////////////////////////////////////////////////////////////////////////////// + //// State handled exclusively by the worker thread + + /** + * Current target file associated to the input and output streams. + */ + nsCOMPtr mActualTarget; + + /** + * Indicates whether mActualTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mActualTargetKeepPartial{false}; + + /** + * Used to calculate the file hash. This keeps state across file renames and + * is lazily initialized in ProcessStateChange. + */ + Maybe mDigest; + + ////////////////////////////////////////////////////////////////////////////// + //// Private methods + + /** + * Called when NS_AsyncCopy completes. + * + * @param aClosure + * Populated with a raw pointer to the BackgroundFileSaver object. + * @param aStatus + * Success or failure status specified when the copy was interrupted. + */ + static void AsyncCopyCallback(void* aClosure, nsresult aStatus); + + /** + * Called on the control thread after state changes, to ensure that the worker + * thread will process the state change appropriately. + * + * @param aShouldInterruptCopy + * If true, the current NS_AsyncCopy, if any, is canceled. + */ + nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); + + /** + * Event called on the worker thread to begin processing a state change. + */ + nsresult ProcessAttention(); + + /** + * Called by ProcessAttention to execute the operations corresponding to the + * state change. If this results in an error, ProcessAttention will force the + * entire operation to be aborted. + */ + nsresult ProcessStateChange(); + + /** + * Returns true if completion conditions are met on the worker thread. The + * first time this happens, posts the completion event to the control thread. + */ + bool CheckCompletion(); + + /** + * Event called on the control thread to indicate that file contents will now + * be saved to the specified file. + */ + nsresult NotifyTargetChange(nsIFile* aTarget); + + /** + * Event called on the control thread to send the final notification. + */ + nsresult NotifySaveComplete(); + + /** + * Verifies the signature of the binary at the specified file path and stores + * the signature data in mSignatureInfo. We extract only X.509 certificates, + * since that is what Google's Safebrowsing protocol specifies. + */ + nsresult ExtractSignatureInfo(const nsAString& filePath); +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +class BackgroundFileSaverOutputStream : public BackgroundFileSaver, + public nsIAsyncOutputStream, + public nsIOutputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSIOUTPUTSTREAMCALLBACK + + BackgroundFileSaverOutputStream(); + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverOutputStream() = default; + + /** + * Original callback provided to our AsyncWait wrapper. + */ + nsCOMPtr mAsyncWaitCallback; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener. This class is instantiated by +// nsExternalHelperAppService, DownloadCore.sys.mjs, and possibly others. + +class BackgroundFileSaverStreamListener final : public BackgroundFileSaver, + public nsIStreamListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + BackgroundFileSaverStreamListener() = default; + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverStreamListener() = default; + + /** + * Protects the state related to whether the request should be suspended. + */ + mozilla::Mutex mSuspensionLock{ + "BackgroundFileSaverStreamListener.mSuspensionLock"}; + + /** + * Whether we should suspend the request because we received too much data. + */ + bool mReceivedTooMuchData MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Request for which we received too much data. This is populated when + * mReceivedTooMuchData becomes true for the first time. + */ + nsCOMPtr mRequest MOZ_GUARDED_BY(mSuspensionLock); + + /** + * Whether mRequest is currently suspended. + */ + bool mRequestSuspended MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Called while NS_AsyncCopy is copying data. + */ + static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount); + + /** + * Called on the control thread to suspend or resume the request. + */ + nsresult NotifySuspendOrResume(); +}; + +// A wrapper around nsIOutputStream, so that we can compute hashes on the +// stream without copying and without polluting pristine NSS code with XPCOM +// interfaces. +class DigestOutputStream : public nsIOutputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + // Constructor. Neither parameter may be null. The caller owns both. + DigestOutputStream(nsIOutputStream* aStream, Digest& aDigest); + + private: + virtual ~DigestOutputStream() = default; + + // Calls to write are passed to this stream. + nsCOMPtr mOutputStream; + // Digest used to compute the hash, owned by the caller. + Digest& mDigest; + + // Don't accidentally copy construct. + DigestOutputStream(const DigestOutputStream& d) = delete; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/CacheInfoIPCTypes.h b/netwerk/base/CacheInfoIPCTypes.h new file mode 100644 index 0000000000..b3d2277335 --- /dev/null +++ b/netwerk/base/CacheInfoIPCTypes.h @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_CacheInfoIPCTypes_h_ +#define mozilla_net_CacheInfoIPCTypes_h_ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsICacheInfoChannel.h" + +namespace IPC { + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType, + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::NONE, + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType:: + SERIALIZE> {}; + +} // namespace IPC + +#endif // mozilla_net_CacheInfoIPCTypes_h_ diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp new file mode 100644 index 0000000000..97a1dde929 --- /dev/null +++ b/netwerk/base/CaptivePortalService.cpp @@ -0,0 +1,428 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" +#include "xpcpublic.h" +#include "xpcprivate.h" + +static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns; + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kAbortCaptivePortalLoginEvent[] = + "captive-portal-login-abort"; +static const char kCaptivePortalLoginSuccessEvent[] = + "captive-portal-login-success"; + +namespace mozilla { +namespace net { + +static LazyLogModule gCaptivePortalLog("CaptivePortalService"); +#undef LOG +#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver, + nsISupportsWeakReference, nsITimerCallback, + nsICaptivePortalCallback, nsINamed) + +static StaticRefPtr gCPService; + +// static +already_AddRefed CaptivePortalService::GetSingleton() { + if (gCPService) { + return do_AddRef(gCPService); + } + + gCPService = new CaptivePortalService(); + ClearOnShutdown(&gCPService); + return do_AddRef(gCPService); +} + +CaptivePortalService::CaptivePortalService() { + mLastChecked = TimeStamp::Now(); +} + +CaptivePortalService::~CaptivePortalService() { + LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n", + XRE_GetProcessType() == GeckoProcessType_Default)); +} + +nsresult CaptivePortalService::PerformCheck() { + LOG( + ("CaptivePortalService::PerformCheck mRequestInProgress:%d " + "mInitialized:%d mStarted:%d\n", + mRequestInProgress, mInitialized, mStarted)); + // Don't issue another request if last one didn't complete + if (mRequestInProgress || !mInitialized || !mStarted) { + return NS_OK; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will + // lead to a crash (see bug 1800603) + // We can remove this restriction when we rewrite the detector in + // C++ or rust (bug 1809886). + if (!XPCJSRuntime::Get()) { + return NS_ERROR_NOT_INITIALIZED; + } + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + nsresult rv; + if (!mCaptivePortalDetector) { + mCaptivePortalDetector = + do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv); + if (NS_FAILED(rv)) { + LOG(("Unable to get a captive portal detector\n")); + return rv; + } + } + + LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n")); + mRequestInProgress = true; + mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this); + return NS_OK; +} + +nsresult CaptivePortalService::RearmTimer() { + LOG(("CaptivePortalService::RearmTimer\n")); + // Start a timer to recheck + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + if (mTimer) { + mTimer->Cancel(); + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + mTimer = nullptr; + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + // If we have successfully determined the state, and we have never detected + // a captive portal, we don't need to keep polling, but will rely on events + // to trigger detection. + if (mState == NOT_CAPTIVE) { + return NS_OK; + } + + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + if (mTimer && mDelay > 0) { + LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay)); + return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +nsresult CaptivePortalService::Initialize() { + if (mInitialized) { + return NS_OK; + } + mInitialized = true; + + // Only the main process service should actually do anything. The service in + // the content process only mirrors the CP state in the main process. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + } + + LOG(("Initialized CaptivePortalService\n")); + return NS_OK; +} + +nsresult CaptivePortalService::Start() { + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (xpc::AreNonLocalConnectionsDisabled() && + !Preferences::GetBool("network.captive-portal-service.testMode", false)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + if (mStarted) { + return NS_OK; + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN"); + mStarted = true; + mEverBeenCaptive = false; + + // Get the delay prefs + Preferences::GetUint("network.captive-portal-service.minInterval", + &mMinInterval); + Preferences::GetUint("network.captive-portal-service.maxInterval", + &mMaxInterval); + Preferences::GetFloat("network.captive-portal-service.backoffFactor", + &mBackoffFactor); + + LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval, + mMaxInterval, mBackoffFactor)); + + mSlackCount = 0; + mDelay = mMinInterval; + + // When Start is called, perform a check immediately + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +nsresult CaptivePortalService::Stop() { + LOG(("CaptivePortalService::Stop\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything when called in the content process. + return NS_OK; + } + + if (!mStarted) { + return NS_OK; + } + + if (mTimer) { + mTimer->Cancel(); + } + mTimer = nullptr; + mRequestInProgress = false; + mStarted = false; + mEverBeenCaptive = false; + if (mCaptivePortalDetector) { + mCaptivePortalDetector->Abort(kInterfaceName); + } + mCaptivePortalDetector = nullptr; + + // Clear the state in case anyone queries the state while detection is off. + mState = UNKNOWN; + return NS_OK; +} + +void CaptivePortalService::SetStateInChild(int32_t aState) { + // This should only be called in the content process, from ContentChild.cpp + // in order to mirror the captive portal state set in the chrome process. + MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default); + + mState = aState; + mLastChecked = TimeStamp::Now(); +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalService +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetState(int32_t* aState) { + *aState = mState; + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::RecheckCaptivePortal() { + LOG(("CaptivePortalService::RecheckCaptivePortal\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + // This is called for user activity. We need to reset the slack count, + // so the checks continue to be quite frequent. + mSlackCount = 0; + mDelay = mMinInterval; + + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) { + double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds(); + *aLastChecked = static_cast(duration); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsITimer +// This callback gets called every mDelay miliseconds +// It issues a checkCaptivePortal operation if one isn't already in progress +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Notify(nsITimer* aTimer) { + LOG(("CaptivePortalService::Notify\n")); + MOZ_ASSERT(aTimer == mTimer); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + PerformCheck(); + + // This is needed because we don't want to always make requests very often. + // Every 10 checks, we the delay is increased mBackoffFactor times + // to a maximum delay of mMaxInterval + mSlackCount++; + if (mSlackCount % 10 == 0) { + mDelay = mDelay * mBackoffFactor; + } + if (mDelay > mMaxInterval) { + mDelay = mMaxInterval; + } + + // Note - if mDelay is 0, the timer will not be rearmed. + RearmTimer(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsINamed +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetName(nsACString& aName) { + aName.AssignLiteral("CaptivePortalService"); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsIObserver +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic)); + if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { + // A redirect or altered content has been detected. + // The user needs to log in. We are in a captive portal. + StateTransition(LOCKED_PORTAL); + mLastChecked = TimeStamp::Now(); + mEverBeenCaptive = true; + } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { + // The user has successfully logged in. We have connectivity. + StateTransition(UNLOCKED_PORTAL); + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + mDelay = mMinInterval; + + RearmTimer(); + } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { + // The login has been aborted + StateTransition(UNKNOWN); + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Stop(); + return NS_OK; + } + + // Send notification so that the captive portal state is mirrored in the + // content process. + nsCOMPtr observerService = services::GetObserverService(); + if (observerService) { + nsCOMPtr cps(this); + observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, + nullptr); + } + + return NS_OK; +} + +void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) { + nsCOMPtr observerService = services::GetObserverService(); + if (observerService) { + nsCOMPtr cps(this); + observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY, + aCaptive ? u"captive" : u"clear"); + } +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalCallback +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Prepare() { + LOG(("CaptivePortalService::Prepare\n")); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_OK; + } + // XXX: Finish preparation shouldn't be called until dns and routing is + // available. + if (mCaptivePortalDetector) { + mCaptivePortalDetector->FinishPreparation(kInterfaceName); + } + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::Complete(bool success) { + LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, + mState)); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + mLastChecked = TimeStamp::Now(); + + // Note: this callback gets called when: + // 1. the request is completed, and content is valid (success == true) + // 2. when the request is aborted or times out (success == false) + + if (success) { + if (mEverBeenCaptive) { + StateTransition(UNLOCKED_PORTAL); + NotifyConnectivityAvailable(true); + } else { + StateTransition(NOT_CAPTIVE); + NotifyConnectivityAvailable(false); + } + } + + mRequestInProgress = false; + return NS_OK; +} + +void CaptivePortalService::StateTransition(int32_t aNewState) { + int32_t oldState = mState; + mState = aNewState; + + if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) || + (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) { + nsCOMPtr observerService = + services::GetObserverService(); + if (observerService) { + nsCOMPtr cps(this); + observerService->NotifyObservers( + cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr); + } + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h new file mode 100644 index 0000000000..f0a7200b4b --- /dev/null +++ b/netwerk/base/CaptivePortalService.h @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CaptivePortalService_h_ +#define CaptivePortalService_h_ + +#include "nsICaptivePortalService.h" +#include "nsICaptivePortalDetector.h" +#include "nsINamed.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsITimer.h" +#include "nsCOMArray.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace net { + +class CaptivePortalService : public nsICaptivePortalService, + public nsIObserver, + public nsSupportsWeakReference, + public nsITimerCallback, + public nsICaptivePortalCallback, + public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAPTIVEPORTALSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICAPTIVEPORTALCALLBACK + NS_DECL_NSINAMED + + nsresult Initialize(); + nsresult Start(); + nsresult Stop(); + + static already_AddRefed GetSingleton(); + + // This method is only called in the content process, in order to mirror + // the captive portal state in the parent process. + void SetStateInChild(int32_t aState); + + private: + static const uint32_t kDefaultInterval = 60 * 1000; // check every 60 seconds + + CaptivePortalService(); + virtual ~CaptivePortalService(); + nsresult PerformCheck(); + nsresult RearmTimer(); + void NotifyConnectivityAvailable(bool aCaptive); + + nsCOMPtr mCaptivePortalDetector; + int32_t mState{UNKNOWN}; + + nsCOMPtr mTimer; + bool mStarted{false}; + bool mInitialized{false}; + bool mRequestInProgress{false}; + bool mEverBeenCaptive{false}; + + uint32_t mDelay{kDefaultInterval}; + int32_t mSlackCount{0}; + + uint32_t mMinInterval{kDefaultInterval}; + uint32_t mMaxInterval{25 * kDefaultInterval}; + float mBackoffFactor{5.0}; + + void StateTransition(int32_t aNewState); + + // This holds a timestamp when the last time when the captive portal check + // has changed state. + mozilla::TimeStamp mLastChecked; +}; + +} // namespace net +} // namespace mozilla + +#endif // CaptivePortalService_h_ diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp new file mode 100644 index 0000000000..bc1619ee60 --- /dev/null +++ b/netwerk/base/Dashboard.cpp @@ -0,0 +1,1182 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http:mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/NetDashboardBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/net/Dashboard.h" +#include "mozilla/net/HttpInfo.h" +#include "mozilla/net/HTTPSSVC.h" +#include "mozilla/net/SocketProcessParent.h" +#include "nsHttp.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsIDNSByTypeRecord.h" +#include "nsIInputStream.h" +#include "nsINamed.h" +#include "nsINetAddr.h" +#include "nsISocketTransport.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsURLHelper.h" +#include "mozilla/Logging.h" +#include "nsIOService.h" +#include "../cache2/CacheFileUtils.h" + +using mozilla::AutoSafeJSContext; +using mozilla::dom::Sequence; +using mozilla::dom::ToJSValue; + +namespace mozilla { +namespace net { + +class SocketData : public nsISupports { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + SocketData() = default; + + uint64_t mTotalSent{0}; + uint64_t mTotalRecv{0}; + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; + + private: + virtual ~SocketData() = default; +}; + +static void GetErrorString(nsresult rv, nsAString& errorString); + +NS_IMPL_ISUPPORTS0(SocketData) + +class HttpData : public nsISupports { + virtual ~HttpData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + HttpData() = default; + + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(HttpData) + +class WebSocketRequest : public nsISupports { + virtual ~WebSocketRequest() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WebSocketRequest() = default; + + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(WebSocketRequest) + +class DnsData : public nsISupports { + virtual ~DnsData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + DnsData() = default; + + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(DnsData) + +class ConnectionData : public nsITransportEventSink, + public nsITimerCallback, + public nsINamed { + virtual ~ConnectionData() { + if (mTimer) { + mTimer->Cancel(); + } + } + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSITIMERCALLBACK + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("net::ConnectionData"); + return NS_OK; + } + + void StartTimer(uint32_t aTimeout); + void StopTimer(); + + explicit ConnectionData(Dashboard* target) { mDashboard = target; } + + nsCOMPtr mSocket; + nsCOMPtr mStreamIn; + nsCOMPtr mTimer; + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; + Dashboard* mDashboard; + + nsCString mHost; + uint32_t mPort{0}; + nsCString mProtocol; + uint32_t mTimeout{0}; + + nsString mStatus; +}; + +NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback, + nsINamed) + +class RcwnData : public nsISupports { + virtual ~RcwnData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + RcwnData() = default; + + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(RcwnData) + +NS_IMETHODIMP +ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, + int64_t aProgress, int64_t aProgressMax) { + if (aStatus == NS_NET_STATUS_CONNECTED_TO) { + StopTimer(); + } + + GetErrorString(aStatus, mStatus); + mEventTarget->Dispatch(NewRunnableMethod>( + "net::Dashboard::GetConnectionStatus", mDashboard, + &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +ConnectionData::Notify(nsITimer* aTimer) { + MOZ_ASSERT(aTimer == mTimer); + + if (mSocket) { + mSocket->Close(NS_ERROR_ABORT); + mSocket = nullptr; + mStreamIn = nullptr; + } + + mTimer = nullptr; + + mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT"); + mEventTarget->Dispatch(NewRunnableMethod>( + "net::Dashboard::GetConnectionStatus", mDashboard, + &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void ConnectionData::StartTimer(uint32_t aTimeout) { + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT); +} + +void ConnectionData::StopTimer() { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +class LookupHelper; + +class LookupArgument : public nsISupports { + virtual ~LookupArgument() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) { + mRecord = aRecord; + mHelper = aHelper; + } + + nsCOMPtr mRecord; + RefPtr mHelper; +}; + +NS_IMPL_ISUPPORTS0(LookupArgument) + +class LookupHelper final : public nsIDNSListener { + virtual ~LookupHelper() { + if (mCancel) { + mCancel->Cancel(NS_ERROR_ABORT); + } + } + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + LookupHelper() = default; + + nsresult ConstructAnswer(LookupArgument* aArgument); + nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument); + + public: + nsCOMPtr mCancel; + nsMainThreadPtrHandle mCallback; + nsIEventTarget* mEventTarget{nullptr}; + nsresult mStatus{NS_ERROR_NOT_INITIALIZED}; +}; + +NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener) + +NS_IMETHODIMP +LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, + nsresult aStatus) { + MOZ_ASSERT(aRequest == mCancel); + mCancel = nullptr; + mStatus = aStatus; + + nsCOMPtr httpsRecord = do_QueryInterface(aRecord); + if (httpsRecord) { + RefPtr arg = new LookupArgument(aRecord, this); + mEventTarget->Dispatch( + NewRunnableMethod>( + "net::LookupHelper::ConstructHTTPSRRAnswer", this, + &LookupHelper::ConstructHTTPSRRAnswer, arg), + NS_DISPATCH_NORMAL); + return NS_OK; + } + + RefPtr arg = new LookupArgument(aRecord, this); + mEventTarget->Dispatch(NewRunnableMethod>( + "net::LookupHelper::ConstructAnswer", this, + &LookupHelper::ConstructAnswer, arg), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) { + nsIDNSRecord* aRecord = aArgument->mRecord; + AutoSafeJSContext cx; + + mozilla::dom::DNSLookupDict dict; + dict.mAddress.Construct(); + + Sequence& addresses = dict.mAddress.Value(); + nsCOMPtr record = do_QueryInterface(aRecord); + if (NS_SUCCEEDED(mStatus) && record) { + dict.mAnswer = true; + bool hasMore; + record->HasMore(&hasMore); + while (hasMore) { + nsString* nextAddress = addresses.AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString nextAddressASCII; + record->GetNextAddrAsString(nextAddressASCII); + CopyASCIItoUTF16(nextAddressASCII, *nextAddress); + record->HasMore(&hasMore); + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + this->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +static void CStringToHexString(const nsACString& aIn, nsAString& aOut) { + static const char* const lut = "0123456789ABCDEF"; + + size_t len = aIn.Length(); + + aOut.SetCapacity(2 * len); + for (size_t i = 0; i < aIn.Length(); ++i) { + const char c = static_cast(aIn[i]); + aOut.Append(lut[(c >> 4) & 0x0F]); + aOut.Append(lut[c & 15]); + } +} + +nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) { + nsCOMPtr httpsRecord = + do_QueryInterface(aArgument->mRecord); + + AutoSafeJSContext cx; + + mozilla::dom::HTTPSRRLookupDict dict; + dict.mRecords.Construct(); + + Sequence& records = dict.mRecords.Value(); + if (NS_SUCCEEDED(mStatus) && httpsRecord) { + dict.mAnswer = true; + nsTArray> svcbRecords; + httpsRecord->GetRecords(svcbRecords); + + for (const auto& record : svcbRecords) { + dom::HTTPSRecord* nextRecord = records.AppendElement(fallible); + if (!nextRecord) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Unused << record->GetPriority(&nextRecord->mPriority); + nsCString name; + Unused << record->GetName(name); + CopyASCIItoUTF16(name, nextRecord->mTargetName); + + nsTArray> values; + Unused << record->GetValues(values); + if (values.IsEmpty()) { + continue; + } + + for (const auto& value : values) { + uint16_t type; + Unused << value->GetType(&type); + switch (type) { + case SvcParamKeyAlpn: { + nextRecord->mAlpn.Construct(); + nextRecord->mAlpn.Value().mType = type; + nsCOMPtr alpnParam = do_QueryInterface(value); + nsTArray alpn; + Unused << alpnParam->GetAlpn(alpn); + nsAutoCString alpnStr; + for (const auto& str : alpn) { + alpnStr.Append(str); + alpnStr.Append(','); + } + CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1), + nextRecord->mAlpn.Value().mAlpn); + break; + } + case SvcParamKeyNoDefaultAlpn: { + nextRecord->mNoDefaultAlpn.Construct(); + nextRecord->mNoDefaultAlpn.Value().mType = type; + break; + } + case SvcParamKeyPort: { + nextRecord->mPort.Construct(); + nextRecord->mPort.Value().mType = type; + nsCOMPtr portParam = do_QueryInterface(value); + Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort); + break; + } + case SvcParamKeyIpv4Hint: { + nextRecord->mIpv4Hint.Construct(); + nextRecord->mIpv4Hint.Value().mType = type; + nsCOMPtr ipv4Param = do_QueryInterface(value); + nsTArray> ipv4Hint; + Unused << ipv4Param->GetIpv4Hint(ipv4Hint); + if (!ipv4Hint.IsEmpty()) { + nextRecord->mIpv4Hint.Value().mAddress.Construct(); + for (const auto& address : ipv4Hint) { + nsString* nextAddress = nextRecord->mIpv4Hint.Value() + .mAddress.Value() + .AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString addressASCII; + Unused << address->GetAddress(addressASCII); + CopyASCIItoUTF16(addressASCII, *nextAddress); + } + } + break; + } + case SvcParamKeyIpv6Hint: { + nextRecord->mIpv6Hint.Construct(); + nextRecord->mIpv6Hint.Value().mType = type; + nsCOMPtr ipv6Param = do_QueryInterface(value); + nsTArray> ipv6Hint; + Unused << ipv6Param->GetIpv6Hint(ipv6Hint); + if (!ipv6Hint.IsEmpty()) { + nextRecord->mIpv6Hint.Value().mAddress.Construct(); + for (const auto& address : ipv6Hint) { + nsString* nextAddress = nextRecord->mIpv6Hint.Value() + .mAddress.Value() + .AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString addressASCII; + Unused << address->GetAddress(addressASCII); + CopyASCIItoUTF16(addressASCII, *nextAddress); + } + } + break; + } + case SvcParamKeyEchConfig: { + nextRecord->mEchConfig.Construct(); + nextRecord->mEchConfig.Value().mType = type; + nsCOMPtr echConfigParam = + do_QueryInterface(value); + nsCString echConfigStr; + Unused << echConfigParam->GetEchconfig(echConfigStr); + CStringToHexString(echConfigStr, + nextRecord->mEchConfig.Value().mEchConfig); + break; + } + case SvcParamKeyODoHConfig: { + nextRecord->mODoHConfig.Construct(); + nextRecord->mODoHConfig.Value().mType = type; + nsCOMPtr ODoHConfigParam = + do_QueryInterface(value); + nsCString ODoHConfigStr; + Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr); + CStringToHexString(ODoHConfigStr, + nextRecord->mODoHConfig.Value().mODoHConfig); + break; + } + default: + break; + } + } + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + this->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier) + +Dashboard::Dashboard() { mEnableLogging = false; } + +NS_IMETHODIMP +Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) { + RefPtr socketData = new SocketData(); + socketData->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + socketData->mEventTarget = GetCurrentSerialEventTarget(); + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr self(this); + SocketProcessParent::GetSingleton()->SendGetSocketData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, + socketData{std::move(socketData)}](SocketDataArgs&& args) { + socketData->mData.Assign(args.info()); + socketData->mTotalSent = args.totalSent(); + socketData->mTotalRecv = args.totalRecv(); + socketData->mEventTarget->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetSockets", self, &Dashboard::GetSockets, + socketData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetSocketsDispatch", this, + &Dashboard::GetSocketsDispatch, socketData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) { + RefPtr socketData = aSocketData; + if (gSocketTransportService) { + gSocketTransportService->GetSocketConnections(&socketData->mData); + socketData->mTotalSent = gSocketTransportService->GetSentBytes(); + socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes(); + } + socketData->mEventTarget->Dispatch( + NewRunnableMethod>("net::Dashboard::GetSockets", this, + &Dashboard::GetSockets, socketData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetSockets(SocketData* aSocketData) { + RefPtr socketData = aSocketData; + AutoSafeJSContext cx; + + mozilla::dom::SocketsDict dict; + dict.mSockets.Construct(); + dict.mSent = 0; + dict.mReceived = 0; + + Sequence& sockets = dict.mSockets.Value(); + + uint32_t length = socketData->mData.Length(); + if (!sockets.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < socketData->mData.Length(); i++) { + dom::SocketElement& mSocket = *sockets.AppendElement(fallible); + CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost); + mSocket.mPort = socketData->mData[i].port; + mSocket.mActive = socketData->mData[i].active; + CopyASCIItoUTF16(socketData->mData[i].type, mSocket.mType); + mSocket.mSent = (double)socketData->mData[i].sent; + mSocket.mReceived = (double)socketData->mData[i].received; + dict.mSent += socketData->mData[i].sent; + dict.mReceived += socketData->mData[i].received; + } + + dict.mSent += socketData->mTotalSent; + dict.mReceived += socketData->mTotalRecv; + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE; + socketData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) { + RefPtr httpData = new HttpData(); + httpData->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + httpData->mEventTarget = GetCurrentSerialEventTarget(); + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr self(this); + SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, httpData](nsTArray&& params) { + httpData->mData.Assign(std::move(params)); + self->GetHttpConnections(httpData); + httpData->mEventTarget->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetHttpConnections", self, + &Dashboard::GetHttpConnections, httpData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch(NewRunnableMethod>( + "net::Dashboard::GetHttpDispatch", this, + &Dashboard::GetHttpDispatch, httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) { + RefPtr httpData = aHttpData; + HttpInfo::GetHttpConnectionData(&httpData->mData); + httpData->mEventTarget->Dispatch( + NewRunnableMethod>("net::Dashboard::GetHttpConnections", + this, &Dashboard::GetHttpConnections, + httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) { + RefPtr httpData = aHttpData; + AutoSafeJSContext cx; + + mozilla::dom::HttpConnDict dict; + dict.mConnections.Construct(); + + using mozilla::dom::DnsAndSockInfoDict; + using mozilla::dom::HttpConnectionElement; + using mozilla::dom::HttpConnInfo; + Sequence& connections = dict.mConnections.Value(); + + uint32_t length = httpData->mData.Length(); + if (!connections.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < httpData->mData.Length(); i++) { + HttpConnectionElement& connection = *connections.AppendElement(fallible); + + CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost); + connection.mPort = httpData->mData[i].port; + CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion); + connection.mSsl = httpData->mData[i].ssl; + + connection.mActive.Construct(); + connection.mIdle.Construct(); + connection.mDnsAndSocks.Construct(); + + Sequence& active = connection.mActive.Value(); + Sequence& idle = connection.mIdle.Value(); + Sequence& dnsAndSocks = connection.mDnsAndSocks.Value(); + + if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) || + !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) || + !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(), + fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) { + HttpConnInfo& info = *active.AppendElement(fallible); + info.mRtt = httpData->mData[i].active[j].rtt; + info.mTtl = httpData->mData[i].active[j].ttl; + info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) { + HttpConnInfo& info = *idle.AppendElement(fallible); + info.mRtt = httpData->mData[i].idle[j].rtt; + info.mTtl = httpData->mData[i].idle[j].ttl; + info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) { + DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible); + info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative; + } + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + httpData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::GetEnableLogging(bool* value) { + *value = mEnableLogging; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::SetEnableLogging(const bool value) { + mEnableLogging = value; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + LogData mData(nsCString(aHost), aSerial, aEncrypted); + if (mWs.data.Contains(mData)) { + return NS_OK; + } + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mWs.data.AppendElement(mData); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data.RemoveElementAt(index); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, + uint32_t aLength) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data[index].mMsgSent++; + mWs.data[index].mSizeSent += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, + uint32_t aLength) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data[index].mMsgReceived++; + mWs.data[index].mSizeReceived += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) { + RefPtr wsRequest = new WebSocketRequest(); + wsRequest->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + wsRequest->mEventTarget = GetCurrentSerialEventTarget(); + + wsRequest->mEventTarget->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetWebSocketConnections", this, + &Dashboard::GetWebSocketConnections, wsRequest), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) { + RefPtr wsRequest = aWsRequest; + AutoSafeJSContext cx; + + mozilla::dom::WebSocketDict dict; + dict.mWebsockets.Construct(); + Sequence& websockets = + dict.mWebsockets.Value(); + + mozilla::MutexAutoLock lock(mWs.lock); + uint32_t length = mWs.data.Length(); + if (!websockets.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < mWs.data.Length(); i++) { + dom::WebSocketElement& websocket = *websockets.AppendElement(fallible); + CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport); + websocket.mMsgsent = mWs.data[i].mMsgSent; + websocket.mMsgreceived = mWs.data[i].mMsgReceived; + websocket.mSentsize = mWs.data[i].mSizeSent; + websocket.mReceivedsize = mWs.data[i].mSizeReceived; + websocket.mEncrypted = mWs.data[i].mEncrypted; + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + wsRequest->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) { + RefPtr dnsData = new DnsData(); + dnsData->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + + nsresult rv; + dnsData->mData.Clear(); + dnsData->mEventTarget = GetCurrentSerialEventTarget(); + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr self(this); + SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, + dnsData{std::move(dnsData)}](nsTArray&& entries) { + dnsData->mData.Assign(std::move(entries)); + dnsData->mEventTarget->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetDNSCacheEntries", self, + &Dashboard::GetDNSCacheEntries, dnsData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch( + NewRunnableMethod>("net::Dashboard::GetDnsInfoDispatch", + this, &Dashboard::GetDnsInfoDispatch, + dnsData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) { + RefPtr dnsData = aDnsData; + if (mDnsService) { + mDnsService->GetDNSCacheEntries(&dnsData->mData); + } + dnsData->mEventTarget->Dispatch( + NewRunnableMethod>("net::Dashboard::GetDNSCacheEntries", + this, &Dashboard::GetDNSCacheEntries, + dnsData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) { + AutoSafeJSContext cx; + + mozilla::dom::DNSCacheDict dict; + dict.mEntries.Construct(); + Sequence& 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& addrs = entry.mHostaddr.Value(); + if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname); + entry.mExpiration = dnsData->mData[i].expiration; + entry.mTrr = dnsData->mData[i].TRR; + + for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) { + nsString* addr = addrs.AppendElement(fallible); + if (!addr) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr); + } + + if (dnsData->mData[i].family == PR_AF_INET6) { + entry.mFamily.AssignLiteral(u"ipv6"); + } else { + entry.mFamily.AssignLiteral(u"ipv4"); + } + + entry.mOriginAttributesSuffix = + NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix); + entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags); + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + dnsData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSLookup(const nsACString& aHost, + nsINetDashboardCallback* aCallback) { + nsresult rv; + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr helper = new LookupHelper(); + helper->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + helper->mEventTarget = GetCurrentSerialEventTarget(); + OriginAttributes attrs; + rv = mDnsService->AsyncResolveNative( + aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), + NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); + return rv; +} + +NS_IMETHODIMP +Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost, + nsINetDashboardCallback* aCallback) { + nsresult rv; + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr helper = new LookupHelper(); + helper->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + helper->mEventTarget = GetCurrentSerialEventTarget(); + OriginAttributes attrs; + rv = mDnsService->AsyncResolveNative( + aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), + NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); + return rv; +} + +NS_IMETHODIMP +Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) { + RefPtr rcwnData = new RcwnData(); + rcwnData->mEventTarget = GetCurrentSerialEventTarget(); + rcwnData->mCallback = new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + + return rcwnData->mEventTarget->Dispatch( + NewRunnableMethod>("net::Dashboard::GetRcwnData", this, + &Dashboard::GetRcwnData, rcwnData), + NS_DISPATCH_NORMAL); +} + +nsresult Dashboard::GetRcwnData(RcwnData* aData) { + AutoSafeJSContext cx; + mozilla::dom::RcwnStatus dict; + + dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber(); + dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber(); + dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber(); + + uint32_t cacheSlow, cacheNotSlow; + CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow); + dict.mCacheSlowCount = cacheSlow; + dict.mCacheNotSlowCount = cacheNotSlow; + + dict.mPerfStats.Construct(); + Sequence& perfStats = dict.mPerfStats.Value(); + uint32_t length = CacheFileUtils::CachePerfStats::LAST; + if (!perfStats.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < length; i++) { + CacheFileUtils::CachePerfStats::EDataType perfType = + static_cast(i); + dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible); + elem.mAvgShort = + CacheFileUtils::CachePerfStats::GetAverage(perfType, false); + elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true); + elem.mStddevLong = + CacheFileUtils::CachePerfStats::GetStdDev(perfType, true); + } + + JS::Rooted val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + aData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) { + switch (pv) { + case HttpVersion::v0_9: + protocolVersion.AssignLiteral(u"http/0.9"); + break; + case HttpVersion::v1_0: + protocolVersion.AssignLiteral(u"http/1.0"); + break; + case HttpVersion::v1_1: + protocolVersion.AssignLiteral(u"http/1.1"); + break; + case HttpVersion::v2_0: + protocolVersion.AssignLiteral(u"http/2"); + break; + case HttpVersion::v3_0: + protocolVersion.AssignLiteral(u"http/3"); + break; + default: + protocolVersion.AssignLiteral(u"unknown protocol version"); + } +} + +NS_IMETHODIMP +Dashboard::GetLogPath(nsACString& aLogPath) { + aLogPath.SetLength(2048); + uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048); + aLogPath.SetLength(len); + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort, + const char* aProtocol, uint32_t aTimeout, + nsINetDashboardCallback* aCallback) { + nsresult rv; + RefPtr connectionData = new ConnectionData(this); + connectionData->mHost = aHost; + connectionData->mPort = aPort; + connectionData->mProtocol = aProtocol; + connectionData->mTimeout = aTimeout; + + connectionData->mCallback = + new nsMainThreadPtrHolder( + "nsINetDashboardCallback", aCallback, true); + connectionData->mEventTarget = GetCurrentSerialEventTarget(); + + rv = TestNewConnection(connectionData); + if (NS_FAILED(rv)) { + mozilla::net::GetErrorString(rv, connectionData->mStatus); + connectionData->mEventTarget->Dispatch( + NewRunnableMethod>( + "net::Dashboard::GetConnectionStatus", this, + &Dashboard::GetConnectionStatus, connectionData), + NS_DISPATCH_NORMAL); + return rv; + } + + return NS_OK; +} + +nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) { + RefPtr connectionData = aConnectionData; + AutoSafeJSContext cx; + + mozilla::dom::ConnStatusDict dict; + dict.mStatus = connectionData->mStatus; + + JS::Rooted 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 = aConnectionData; + + nsresult rv; + if (!connectionData->mHost.Length() || + !net_IsValidHostName(connectionData->mHost)) { + return NS_ERROR_UNKNOWN_HOST; + } + + if (connectionData->mProtocol.EqualsLiteral("ssl")) { + AutoTArray socketTypes = {connectionData->mProtocol}; + rv = gSocketTransportService->CreateTransport( + socketTypes, connectionData->mHost, connectionData->mPort, nullptr, + nullptr, getter_AddRefs(connectionData->mSocket)); + } else { + rv = gSocketTransportService->CreateTransport( + nsTArray(), connectionData->mHost, connectionData->mPort, + nullptr, nullptr, getter_AddRefs(connectionData->mSocket)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->SetEventSink(connectionData, + GetCurrentSerialEventTarget()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->OpenInputStream( + nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(connectionData->mStreamIn)); + if (NS_FAILED(rv)) { + return rv; + } + + connectionData->StartTimer(connectionData->mTimeout); + + return rv; +} + +using ErrorEntry = struct { + nsresult key; + const char* error; +}; + +#undef ERROR +#define ERROR(key, val) \ + { key, #key } + +ErrorEntry socketTransportStatuses[] = { + ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), + ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), + ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), + ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), + ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)), + ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)), + ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), + ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), + ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), +}; +#undef ERROR + +static void GetErrorString(nsresult rv, nsAString& errorString) { + for (auto& socketTransportStatus : socketTransportStatuses) { + if (socketTransportStatus.key == rv) { + errorString.AssignASCII(socketTransportStatus.error); + return; + } + } + nsAutoCString errorCString; + mozilla::GetErrorName(rv, errorCString); + CopyUTF8toUTF16(errorCString, errorString); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h new file mode 100644 index 0000000000..1112a013e9 --- /dev/null +++ b/netwerk/base/Dashboard.h @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDashboard_h__ +#define nsDashboard_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/net/DashboardTypes.h" +#include "nsIDashboard.h" +#include "nsIDashboardEventNotifier.h" + +class nsIDNSService; + +namespace mozilla { +namespace net { + +class SocketData; +class HttpData; +class DnsData; +class WebSocketRequest; +class ConnectionData; +class RcwnData; + +class Dashboard final : public nsIDashboard, public nsIDashboardEventNotifier { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDASHBOARD + NS_DECL_NSIDASHBOARDEVENTNOTIFIER + + Dashboard(); + static const char* GetErrorString(nsresult rv); + nsresult GetConnectionStatus(ConnectionData* aConnectionData); + + private: + struct LogData { + LogData(nsCString host, uint32_t serial, bool encryption) + : mHost(host), + mSerial(serial), + mMsgSent(0), + mMsgReceived(0), + mSizeSent(0), + mSizeReceived(0), + mEncrypted(encryption) {} + nsCString mHost; + uint32_t mSerial; + uint32_t mMsgSent; + uint32_t mMsgReceived; + uint64_t mSizeSent; + uint64_t mSizeReceived; + bool mEncrypted; + bool operator==(const LogData& a) const { + return mHost.Equals(a.mHost) && (mSerial == a.mSerial); + } + }; + + struct WebSocketData { + WebSocketData() : lock("Dashboard.webSocketData") {} + uint32_t IndexOf(const nsCString& hostname, uint32_t mSerial) { + LogData temp(hostname, mSerial, false); + return data.IndexOf(temp); + } + nsTArray data; + mozilla::Mutex lock MOZ_UNANNOTATED; + }; + + bool mEnableLogging; + WebSocketData mWs; + + private: + virtual ~Dashboard() = default; + + nsresult GetSocketsDispatch(SocketData*); + nsresult GetHttpDispatch(HttpData*); + nsresult GetDnsInfoDispatch(DnsData*); + nsresult TestNewConnection(ConnectionData*); + + /* Helper methods that pass the JSON to the callback function. */ + nsresult GetSockets(SocketData*); + nsresult GetHttpConnections(HttpData*); + nsresult GetDNSCacheEntries(DnsData*); + nsresult GetWebSocketConnections(WebSocketRequest*); + nsresult GetRcwnData(RcwnData*); + + nsCOMPtr mDnsService; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsDashboard_h__ diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h new file mode 100644 index 0000000000..6dd901d02a --- /dev/null +++ b/netwerk/base/DashboardTypes.h @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_DashboardTypes_h_ +#define mozilla_net_DashboardTypes_h_ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsHttp.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +struct SocketInfo { + nsCString host; + uint64_t sent; + uint64_t received; + uint16_t port; + bool active; + nsCString type; +}; + +inline bool operator==(const SocketInfo& a, const SocketInfo& b) { + return a.host == b.host && a.sent == b.sent && a.received == b.received && + a.port == b.port && a.active == b.active && a.type == b.type; +} + +struct DnsAndConnectSockets { + bool speculative; +}; + +struct DNSCacheEntries { + nsCString hostname; + nsTArray hostaddr; + uint16_t family; + int64_t expiration; + nsCString netInterface; + bool TRR; + nsCString originAttributesSuffix; + nsCString flags; +}; + +struct HttpConnInfo { + uint32_t ttl; + uint32_t rtt; + nsString protocolVersion; + + void SetHTTPProtocolVersion(HttpVersion pv); +}; + +struct HttpRetParams { + nsCString host; + CopyableTArray active; + CopyableTArray idle; + CopyableTArray dnsAndSocks; + uint32_t counter; + uint16_t port; + nsCString httpVersion; + bool ssl; +}; + +} // namespace net +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::net::SocketInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.host); + WriteParam(aWriter, aParam.sent); + WriteParam(aWriter, aParam.received); + WriteParam(aWriter, aParam.port); + WriteParam(aWriter, aParam.active); + WriteParam(aWriter, aParam.type); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->host) && + ReadParam(aReader, &aResult->sent) && + ReadParam(aReader, &aResult->received) && + ReadParam(aReader, &aResult->port) && + ReadParam(aReader, &aResult->active) && + ReadParam(aReader, &aResult->type); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::net::DNSCacheEntries paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.hostname); + WriteParam(aWriter, aParam.hostaddr); + WriteParam(aWriter, aParam.family); + WriteParam(aWriter, aParam.expiration); + WriteParam(aWriter, aParam.netInterface); + WriteParam(aWriter, aParam.TRR); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->hostname) && + ReadParam(aReader, &aResult->hostaddr) && + ReadParam(aReader, &aResult->family) && + ReadParam(aReader, &aResult->expiration) && + ReadParam(aReader, &aResult->netInterface) && + ReadParam(aReader, &aResult->TRR); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::net::DnsAndConnectSockets paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.speculative); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->speculative); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::net::HttpConnInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.ttl); + WriteParam(aWriter, aParam.rtt); + WriteParam(aWriter, aParam.protocolVersion); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->ttl) && + ReadParam(aReader, &aResult->rtt) && + ReadParam(aReader, &aResult->protocolVersion); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::net::HttpRetParams paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.host); + WriteParam(aWriter, aParam.active); + WriteParam(aWriter, aParam.idle); + WriteParam(aWriter, aParam.dnsAndSocks); + WriteParam(aWriter, aParam.counter); + WriteParam(aWriter, aParam.port); + WriteParam(aWriter, aParam.httpVersion); + WriteParam(aWriter, aParam.ssl); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->host) && + ReadParam(aReader, &aResult->active) && + ReadParam(aReader, &aResult->idle) && + ReadParam(aReader, &aResult->dnsAndSocks) && + ReadParam(aReader, &aResult->counter) && + ReadParam(aReader, &aResult->port) && + ReadParam(aReader, &aResult->httpVersion) && + ReadParam(aReader, &aResult->ssl); + } +}; + +} // namespace IPC + +#endif // mozilla_net_DashboardTypes_h_ diff --git a/netwerk/base/DefaultURI.cpp b/netwerk/base/DefaultURI.cpp new file mode 100644 index 0000000000..f6506e28fb --- /dev/null +++ b/netwerk/base/DefaultURI.cpp @@ -0,0 +1,538 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DefaultURI.h" +#include "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsURLHelper.h" + +#include "mozilla/ipc/URIParams.h" + +namespace mozilla { +namespace net { + +#define NS_DEFAULTURI_CID \ + { /* 04445aa0-fd27-4c99-bd41-6be6318ae92c */ \ + 0x04445aa0, 0xfd27, 0x4c99, { \ + 0xbd, 0x41, 0x6b, 0xe6, 0x31, 0x8a, 0xe9, 0x2c \ + } \ + } + +#define ASSIGN_AND_ADDREF_THIS(ptrToMutator) \ + do { \ + if (ptrToMutator) { \ + *(ptrToMutator) = do_AddRef(this).take(); \ + } \ + } while (0) + +static NS_DEFINE_CID(kDefaultURICID, NS_DEFAULTURI_CID); + +//---------------------------------------------------------------------------- +// nsIClassInfo +//---------------------------------------------------------------------------- + +NS_IMPL_CLASSINFO(DefaultURI, nullptr, nsIClassInfo::THREADSAFE, + NS_DEFAULTURI_CID) +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(DefaultURI) + +//---------------------------------------------------------------------------- +// nsISupports +//---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DefaultURI) +NS_IMPL_RELEASE(DefaultURI) +NS_INTERFACE_TABLE_HEAD(DefaultURI) + NS_INTERFACE_TABLE(DefaultURI, nsIURI, nsISerializable) + NS_INTERFACE_TABLE_TO_MAP_SEGUE + NS_IMPL_QUERY_CLASSINFO(DefaultURI) + if (aIID.Equals(kDefaultURICID)) { + foundInterface = static_cast(this); + } else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------------- +// nsISerializable +//---------------------------------------------------------------------------- + +NS_IMETHODIMP DefaultURI::Read(nsIObjectInputStream* aInputStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DefaultURI::Write(nsIObjectOutputStream* aOutputStream) { + nsAutoCString spec(mURL->Spec()); + return aOutputStream->WriteStringZ(spec.get()); +} + +//---------------------------------------------------------------------------- +// nsISizeOf +//---------------------------------------------------------------------------- + +size_t DefaultURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mURL->SizeOf(); +} + +size_t DefaultURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +//---------------------------------------------------------------------------- +// nsIURI +//---------------------------------------------------------------------------- + +NS_IMETHODIMP DefaultURI::GetSpec(nsACString& aSpec) { + aSpec = mURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPrePath(nsACString& aPrePath) { + aPrePath = mURL->PrePath(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetScheme(nsACString& aScheme) { + aScheme = mURL->Scheme(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetUserPass(nsACString& aUserPass) { + aUserPass = mURL->Username(); + nsAutoCString pass(mURL->Password()); + if (pass.IsEmpty()) { + return NS_OK; + } + aUserPass.Append(':'); + aUserPass.Append(pass); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetUsername(nsACString& aUsername) { + aUsername = mURL->Username(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPassword(nsACString& aPassword) { + aPassword = mURL->Password(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHostPort(nsACString& aHostPort) { + aHostPort = mURL->HostPort(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHost(nsACString& aHost) { + aHost = mURL->Host(); + + // Historically nsIURI.host has always returned an IPv6 address that isn't + // enclosed in brackets. Ideally we want to change that, but for the sake of + // consitency we'll leave it like that for the moment. + // Bug 1603199 should fix this. + if (StringBeginsWith(aHost, "["_ns) && StringEndsWith(aHost, "]"_ns) && + aHost.FindChar(':') != kNotFound) { + aHost = Substring(aHost, 1, aHost.Length() - 2); + } + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPort(int32_t* aPort) { + *aPort = mURL->Port(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPathQueryRef(nsACString& aPathQueryRef) { + aPathQueryRef = mURL->Path(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Equals(nsIURI* other, bool* _retval) { + RefPtr otherUri; + nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri)); + if (NS_FAILED(rv)) { + *_retval = false; + return NS_OK; + } + + *_retval = mURL->Spec() == otherUri->mURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::SchemeIs(const char* scheme, bool* _retval) { + *_retval = mURL->Scheme().Equals(scheme); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Resolve(const nsACString& aRelativePath, + nsACString& aResult) { + nsAutoCString scheme; + nsresult rv = net_ExtractURLScheme(aRelativePath, scheme); + if (NS_SUCCEEDED(rv)) { + aResult = aRelativePath; + return NS_OK; + } + + // We try to create another URL with this one as its base. + RefPtr resolvedURL; + rv = MozURL::Init(getter_AddRefs(resolvedURL), aRelativePath, mURL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If parsing the relative url fails, we revert to the previous behaviour + // and just return the relative path. + aResult = aRelativePath; + return NS_OK; + } + + aResult = resolvedURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetAsciiSpec(nsACString& aAsciiSpec) { + return GetSpec(aAsciiSpec); +} + +NS_IMETHODIMP DefaultURI::GetAsciiHostPort(nsACString& aAsciiHostPort) { + return GetHostPort(aAsciiHostPort); +} + +NS_IMETHODIMP DefaultURI::GetAsciiHost(nsACString& aAsciiHost) { + return GetHost(aAsciiHost); +} + +NS_IMETHODIMP DefaultURI::GetRef(nsACString& aRef) { + aRef = mURL->Ref(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::EqualsExceptRef(nsIURI* other, bool* _retval) { + RefPtr otherUri; + nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri)); + if (NS_FAILED(rv)) { + *_retval = false; + return NS_OK; + } + + *_retval = mURL->SpecNoRef().Equals(otherUri->mURL->SpecNoRef()); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) { + aSpecIgnoringRef = mURL->SpecNoRef(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHasRef(bool* aHasRef) { + *aHasRef = mURL->HasFragment(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetFilePath(nsACString& aFilePath) { + aFilePath = mURL->FilePath(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetQuery(nsACString& aQuery) { + aQuery = mURL->Query(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetDisplayHost(nsACString& aDisplayHost) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetHost(aDisplayHost); +} + +NS_IMETHODIMP DefaultURI::GetDisplayHostPort(nsACString& aDisplayHostPort) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetHostPort(aDisplayHostPort); +} + +NS_IMETHODIMP DefaultURI::GetDisplaySpec(nsACString& aDisplaySpec) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetSpec(aDisplaySpec); +} + +NS_IMETHODIMP DefaultURI::GetDisplayPrePath(nsACString& aDisplayPrePath) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetPrePath(aDisplayPrePath); +} + +NS_IMETHODIMP DefaultURI::Mutate(nsIURIMutator** _retval) { + RefPtr mutator = new DefaultURI::Mutator(); + mutator->Init(this); + mutator.forget(_retval); + return NS_OK; +} + +void DefaultURI::Serialize(ipc::URIParams& aParams) { + ipc::DefaultURIParams params; + params.spec() = mURL->Spec(); + aParams = params; +} + +//---------------------------------------------------------------------------- +// nsIURIMutator +//---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DefaultURI::Mutator) +NS_IMPL_RELEASE(DefaultURI::Mutator) +NS_IMETHODIMP DefaultURI::Mutator::QueryInterface(REFNSIID aIID, + void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!"); + nsISupports* foundInterface = nullptr; + if (aIID.Equals(NS_GET_IID(nsIURI))) { + RefPtr defaultURI = new DefaultURI(); + mMutator->Finalize(getter_AddRefs(defaultURI->mURL)); + foundInterface = + static_cast(static_cast((defaultURI.get()))); + NS_ADDREF(foundInterface); + *aInstancePtr = foundInterface; + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIURIMutator)) || + aIID.Equals(NS_GET_IID(nsISupports))) { + foundInterface = + static_cast(static_cast(this)); + } else if (aIID.Equals(NS_GET_IID(nsIURISetters))) { + foundInterface = + static_cast(static_cast(this)); + } else if (aIID.Equals(NS_GET_IID(nsIURISetSpec))) { + foundInterface = + static_cast(static_cast(this)); + } else if (aIID.Equals(NS_GET_IID(nsISerializable))) { + foundInterface = + static_cast(static_cast(this)); + } + + if (foundInterface) { + NS_ADDREF(foundInterface); + *aInstancePtr = foundInterface; + return NS_OK; + } + + return NS_NOINTERFACE; +} + +NS_IMETHODIMP DefaultURI::Mutator::Read(nsIObjectInputStream* aStream) { + nsAutoCString spec; + nsresult rv = aStream->ReadCString(spec); + if (NS_FAILED(rv)) { + return rv; + } + return SetSpec(spec, nullptr); +} + +NS_IMETHODIMP DefaultURI::Mutator::Deserialize( + const mozilla::ipc::URIParams& aParams) { + if (aParams.type() != ipc::URIParams::TDefaultURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return NS_ERROR_FAILURE; + } + + const ipc::DefaultURIParams& params = aParams.get_DefaultURIParams(); + auto result = MozURL::Mutator::FromSpec(params.spec()); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Mutator::Finalize(nsIURI** aURI) { + if (!mMutator.isSome()) { + return NS_ERROR_NOT_AVAILABLE; + } + RefPtr uri = new DefaultURI(); + mMutator->Finalize(getter_AddRefs(uri->mURL)); + mMutator = Nothing(); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Mutator::SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + auto result = MozURL::Mutator::FromSpec(aSpec); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return NS_OK; +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetScheme(const nsACString& aScheme, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetScheme(aScheme); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetUserPass(const nsACString& aUserPass, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + int32_t index = aUserPass.FindChar(':'); + if (index == kNotFound) { + mMutator->SetUsername(aUserPass); + mMutator->SetPassword(""_ns); + return mMutator->GetStatus(); + } + + mMutator->SetUsername(Substring(aUserPass, 0, index)); + nsresult rv = mMutator->GetStatus(); + if (NS_FAILED(rv)) { + return rv; + } + mMutator->SetPassword(Substring(aUserPass, index + 1)); + rv = mMutator->GetStatus(); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetUsername(const nsACString& aUsername, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetUsername(aUsername); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPassword(const nsACString& aPassword, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetPassword(aPassword); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetHostPort(const nsACString& aHostPort, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetHostPort(aHostPort); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetHost(const nsACString& aHost, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetHostname(aHost); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPort(int32_t aPort, nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetPort(aPort); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPathQueryRef(const nsACString& aPathQueryRef, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + if (aPathQueryRef.IsEmpty()) { + mMutator->SetFilePath(""_ns); + mMutator->SetQuery(""_ns); + mMutator->SetRef(""_ns); + return mMutator->GetStatus(); + } + + nsAutoCString pathQueryRef(aPathQueryRef); + if (!StringBeginsWith(pathQueryRef, "/"_ns)) { + pathQueryRef.Insert('/', 0); + } + + RefPtr url; + mMutator->Finalize(getter_AddRefs(url)); + mMutator = Nothing(); + + auto result = MozURL::Mutator::FromSpec(pathQueryRef, url); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetRef(const nsACString& aRef, nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetRef(aRef); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetFilePath(const nsACString& aFilePath, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetFilePath(aFilePath); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetQuery(const nsACString& aQuery, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetQuery(aQuery); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetQueryWithEncoding(const nsACString& aQuery, + const mozilla::Encoding* aEncoding, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + // we only support UTF-8 for DefaultURI + mMutator->SetQuery(aQuery); + return mMutator->GetStatus(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/DefaultURI.h b/netwerk/base/DefaultURI.h new file mode 100644 index 0000000000..e737067565 --- /dev/null +++ b/netwerk/base/DefaultURI.h @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DefaultURI_h__ +#define DefaultURI_h__ + +#include "nsIURI.h" +#include "nsISerializable.h" +#include "nsISizeOf.h" +#include "nsIURIMutator.h" +#include "mozilla/net/MozURL.h" + +namespace mozilla { +namespace net { + +class DefaultURI : public nsIURI, public nsISerializable, public nsISizeOf { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSISERIALIZABLE + + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + class Mutator final : public nsIURIMutator, public nsISerializable { + NS_DECL_ISUPPORTS + NS_DECL_NSIURISETSPEC + NS_DECL_NSIURISETTERS + NS_DECL_NSIURIMUTATOR + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + MOZ_ASSERT_UNREACHABLE("nsIURIMutator.write() should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + void Init(DefaultURI* aFrom) { mMutator = Some(aFrom->mURL->Mutate()); } + + Maybe mMutator; + + friend class DefaultURI; + }; + + private: + virtual ~DefaultURI() = default; + RefPtr mURL; +}; + +} // namespace net +} // namespace mozilla + +#endif // DefaultURI_h__ diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp new file mode 100644 index 0000000000..87179d6732 --- /dev/null +++ b/netwerk/base/EventTokenBucket.cpp @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EventTokenBucket.h" + +#include "nsICancelable.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransportService2.h" +#ifdef DEBUG +# include "MainThreadUtils.h" +#endif + +#ifdef XP_WIN +# include +# include +#endif + +namespace mozilla { +namespace net { + +//////////////////////////////////////////// +// EventTokenBucketCancelable +//////////////////////////////////////////// + +class TokenBucketCancelable : public nsICancelable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICANCELABLE + + explicit TokenBucketCancelable(class ATokenBucketEvent* event); + void Fire(); + + private: + virtual ~TokenBucketCancelable() = default; + + friend class EventTokenBucket; + ATokenBucketEvent* mEvent; +}; + +NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) + +TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent* event) + : mEvent(event) {} + +NS_IMETHODIMP +TokenBucketCancelable::Cancel(nsresult reason) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mEvent = nullptr; + return NS_OK; +} + +void TokenBucketCancelable::Fire() { + if (!mEvent) return; + + ATokenBucketEvent* event = mEvent; + mEvent = nullptr; + event->OnTokenBucketAdmitted(); +} + +//////////////////////////////////////////// +// EventTokenBucket +//////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback, nsINamed) + +// by default 1hz with no burst +EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize) + : mUnitCost(kUsecPerSec), + mMaxCredit(kUsecPerSec), + mCredit(kUsecPerSec), + mPaused(false), + mStopped(false), + mTimerArmed(false) +#ifdef XP_WIN + , + mFineGrainTimerInUse(false), + mFineGrainResetTimerArmed(false) +#endif +{ + mLastUpdate = TimeStamp::Now(); + + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr sts; + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) { + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + } + if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts); + SetRate(eventsPerSecond, burstSize); +} + +EventTokenBucket::~EventTokenBucket() { + SOCKET_LOG( + ("EventTokenBucket::dtor %p events=%zu\n", this, mEvents.GetSize())); + + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr cancelable = mEvents.PopFront(); + cancelable->Fire(); + } +} + +void EventTokenBucket::CleanupTimers() { + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; + mTimerArmed = false; + +#ifdef XP_WIN + NormalTimers(); + if (mFineGrainResetTimer && mFineGrainResetTimerArmed) { + mFineGrainResetTimer->Cancel(); + } + mFineGrainResetTimer = nullptr; + mFineGrainResetTimerArmed = false; +#endif +} + +void EventTokenBucket::SetRate(uint32_t eventsPerSecond, uint32_t burstSize) { + SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", this, eventsPerSecond, + burstSize)); + + if (eventsPerSecond > kMaxHz) { + eventsPerSecond = kMaxHz; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + if (!eventsPerSecond) { + eventsPerSecond = 1; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + mUnitCost = kUsecPerSec / eventsPerSecond; + mMaxCredit = mUnitCost * burstSize; + if (mMaxCredit > kUsecPerSec * 60 * 15) { + SOCKET_LOG((" burstSize out of range\n")); + mMaxCredit = kUsecPerSec * 60 * 15; + } + mCredit = mMaxCredit; + mLastUpdate = TimeStamp::Now(); +} + +void EventTokenBucket::ClearCredits() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); + mCredit = 0; +} + +uint32_t EventTokenBucket::BurstEventsAvailable() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return static_cast(mCredit / mUnitCost); +} + +uint32_t EventTokenBucket::QueuedEvents() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mEvents.GetSize(); +} + +void EventTokenBucket::Pause() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::Pause %p\n", this)); + if (mPaused || mStopped) return; + + mPaused = true; + if (mTimerArmed) { + mTimer->Cancel(); + mTimerArmed = false; + } +} + +void EventTokenBucket::UnPause() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this)); + if (!mPaused || mStopped) return; + + mPaused = false; + DispatchEvents(); + UpdateTimer(); +} + +void EventTokenBucket::Stop() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed)); + mStopped = true; + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr cancelable = mEvents.PopFront(); + cancelable->Fire(); + } +} + +nsresult EventTokenBucket::SubmitEvent(ATokenBucketEvent* event, + nsICancelable** cancelable) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this)); + + if (mStopped || !mTimer) return NS_ERROR_FAILURE; + + UpdateCredits(); + + RefPtr cancelEvent = new TokenBucketCancelable(event); + // When this function exits the cancelEvent needs 2 references, one for the + // mEvents queue and one for the caller of SubmitEvent() + + *cancelable = do_AddRef(cancelEvent).take(); + + if (mPaused || !TryImmediateDispatch(cancelEvent.get())) { + // queue it + SOCKET_LOG((" queued\n")); + mEvents.Push(cancelEvent.forget()); + UpdateTimer(); + } else { + SOCKET_LOG((" dispatched synchronously\n")); + } + + return NS_OK; +} + +bool EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable* cancelable) { + if (mCredit < mUnitCost) return false; + + mCredit -= mUnitCost; + cancelable->Fire(); + return true; +} + +void EventTokenBucket::DispatchEvents() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused)); + if (mPaused || mStopped) return; + + while (mEvents.GetSize() && mUnitCost <= mCredit) { + RefPtr cancelable = mEvents.PopFront(); + if (cancelable->mEvent) { + SOCKET_LOG( + ("EventTokenBucket::DispachEvents [%p] " + "Dispatching queue token bucket event cost=%" PRIu64 + " credit=%" PRIu64 "\n", + this, mUnitCost, mCredit)); + mCredit -= mUnitCost; + cancelable->Fire(); + } + } + +#ifdef XP_WIN + if (!mEvents.GetSize()) WantNormalTimers(); +#endif +} + +void EventTokenBucket::UpdateTimer() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) { + return; + } + + if (mCredit >= mUnitCost) return; + + // determine the time needed to wait to accumulate enough credits to admit + // one more event and set the timer for that point. Always round it + // up because firing early doesn't help. + // + uint64_t deficit = mUnitCost - mCredit; + uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec; + + if (msecWait < 4) { // minimum wait + msecWait = 4; + } else if (msecWait > 60000) { // maximum wait + msecWait = 60000; + } + +#ifdef XP_WIN + FineGrainTimers(); +#endif + + SOCKET_LOG( + ("EventTokenBucket::UpdateTimer %p for %" PRIu64 "ms\n", this, msecWait)); + nsresult rv = mTimer->InitWithCallback(this, static_cast(msecWait), + nsITimer::TYPE_ONE_SHOT); + mTimerArmed = NS_SUCCEEDED(rv); +} + +NS_IMETHODIMP +EventTokenBucket::Notify(nsITimer* timer) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + +#ifdef XP_WIN + if (timer == mFineGrainResetTimer) { + FineGrainResetTimerNotify(); + return NS_OK; + } +#endif + + SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this)); + mTimerArmed = false; + if (mStopped) return NS_OK; + + UpdateCredits(); + DispatchEvents(); + UpdateTimer(); + + return NS_OK; +} + +NS_IMETHODIMP +EventTokenBucket::GetName(nsACString& aName) { + aName.AssignLiteral("EventTokenBucket"); + return NS_OK; +} + +void EventTokenBucket::UpdateCredits() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + TimeStamp now = TimeStamp::Now(); + TimeDuration elapsed = now - mLastUpdate; + mLastUpdate = now; + + mCredit += static_cast(elapsed.ToMicroseconds()); + if (mCredit > mMaxCredit) mCredit = mMaxCredit; + SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %" PRIu64 " (%" PRIu64 + " each.. %3.2f)\n", + this, mCredit, mUnitCost, (double)mCredit / mUnitCost)); +} + +#ifdef XP_WIN +void EventTokenBucket::FineGrainTimers() { + SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n", + this, mFineGrainTimerInUse)); + + mLastFineGrainTimerUse = TimeStamp::Now(); + + if (mFineGrainTimerInUse) return; + + if (mUnitCost > kCostFineGrainThreshold) return; + + SOCKET_LOG( + ("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", this)); + + mFineGrainTimerInUse = true; + timeBeginPeriod(1); +} + +void EventTokenBucket::NormalTimers() { + if (!mFineGrainTimerInUse) return; + mFineGrainTimerInUse = false; + + SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this)); + timeEndPeriod(1); +} + +void EventTokenBucket::WantNormalTimers() { + if (!mFineGrainTimerInUse) return; + if (mFineGrainResetTimerArmed) return; + + TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse); + static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5); + + if (elapsed >= fiveSeconds) { + NormalTimers(); + return; + } + + if (!mFineGrainResetTimer) mFineGrainResetTimer = NS_NewTimer(); + + // if we can't delay the reset, just do it now + if (!mFineGrainResetTimer) { + NormalTimers(); + return; + } + + // pad the callback out 100ms to avoid having to round trip this again if the + // timer calls back just a tad early. + SOCKET_LOG( + ("EventTokenBucket::WantNormalTimers %p " + "Will reset timer granularity after delay", + this)); + + mFineGrainResetTimer->InitWithCallback( + this, + static_cast((fiveSeconds - elapsed).ToMilliseconds()) + 100, + nsITimer::TYPE_ONE_SHOT); + mFineGrainResetTimerArmed = true; +} + +void EventTokenBucket::FineGrainResetTimerNotify() { + SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify(%p) events = %zd\n", + this, mEvents.GetSize())); + mFineGrainResetTimerArmed = false; + + // If we are currently processing events then wait for the queue to drain + // before trying to reset back to normal timers again + if (!mEvents.GetSize()) WantNormalTimers(); +} + +#endif + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h new file mode 100644 index 0000000000..4206a622f5 --- /dev/null +++ b/netwerk/base/EventTokenBucket.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetEventTokenBucket_h__ +#define NetEventTokenBucket_h__ + +#include "ARefBase.h" +#include "nsCOMPtr.h" +#include "nsDeque.h" +#include "nsINamed.h" +#include "nsITimer.h" + +#include "mozilla/TimeStamp.h" + +class nsICancelable; + +namespace mozilla { +namespace net { + +/* A token bucket is used to govern the maximum rate a series of events + can be executed at. For instance if your event was "eat a piece of cake" + then a token bucket configured to allow "1 piece per day" would spread + the eating of a 8 piece cake over 8 days even if you tried to eat the + whole thing up front. In a practical sense it 'costs' 1 token to execute + an event and tokens are 'earned' at a particular rate as time goes by. + + The token bucket can be perfectly smooth or allow a configurable amount of + burstiness. A bursty token bucket allows you to save up unused credits, while + a perfectly smooth one would not. A smooth "1 per day" cake token bucket + would require 9 days to eat that cake if you skipped a slice on day 4 + (use the token or lose it), while a token bucket configured with a burst + of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day + 5) and finish the cake in the usual 8 days. + + EventTokenBucket(hz=20, burst=5) creates a token bucket with the following + properties: + + + events from an infinite stream will be admitted 20 times per second (i.e. + hz=20 means 1 event per 50 ms). Timers will be used to space things evenly + down to 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than + 200hz will admit multiple events with 5ms gaps between them. 10000hz is the + maximum rate and 1hz is the minimum rate. + + + The burst size controls the limit of 'credits' that a token bucket can + accumulate when idle. For our (20,5) example each event requires 50ms of + credit (again, 20hz = 50ms per event). a burst size of 5 means that the + token bucket can accumulate a maximum of 250ms (5 * 50ms) for this bucket. + If no events have been admitted for the last full second the bucket can + still only accumulate 250ms of credit - but that credit means that 5 events + can be admitted without delay. A burst size of 1 is the minimum. The + EventTokenBucket is created with maximum credits already applied, but they + can be cleared with the ClearCredits() method. The maximum burst size is 15 + minutes worth of events. + + + An event is submitted to the token bucket asynchronously through + SubmitEvent(). The OnTokenBucketAdmitted() method of the submitted event + is used as a callback when the event is ready to run. A cancelable event is + returned to the SubmitEvent() caller for use in the case they do not wish + to wait for the callback. +*/ + +class EventTokenBucket; + +class ATokenBucketEvent { + public: + virtual void OnTokenBucketAdmitted() = 0; +}; + +class TokenBucketCancelable; + +class EventTokenBucket : public nsITimerCallback, + public nsINamed, + public ARefBase { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + // This should be constructed on the main thread + EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize); + + // These public methods are all meant to be called from the socket thread + void ClearCredits(); + uint32_t BurstEventsAvailable(); + uint32_t QueuedEvents(); + + // a paused token bucket will not process any events, but it will accumulate + // credits. ClearCredits can be used before unpausing if desired. + void Pause(); + void UnPause(); + void Stop(); + + // The returned cancelable event can only be canceled from the socket thread + nsresult SubmitEvent(ATokenBucketEvent* event, nsICancelable** cancelable); + + private: + virtual ~EventTokenBucket(); + void CleanupTimers(); + + friend class RunNotifyEvent; + friend class SetTimerEvent; + + bool TryImmediateDispatch(TokenBucketCancelable* cancelable); + void SetRate(uint32_t eventsPerSecond, uint32_t burstSize); + + void DispatchEvents(); + void UpdateTimer(); + void UpdateCredits(); + + const static uint64_t kUsecPerSec = 1000000; + const static uint64_t kUsecPerMsec = 1000; + const static uint64_t kMaxHz = 10000; + + uint64_t + mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond) + uint64_t mMaxCredit; // usec mCredit limit (from busrtSize) + uint64_t mCredit; // usec of accumulated credit. + + bool mPaused; + bool mStopped; + nsRefPtrDeque 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 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 mFineGrainResetTimer; +#endif +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp new file mode 100644 index 0000000000..8a9ee694dd --- /dev/null +++ b/netwerk/base/FuzzyLayer.cpp @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzyLayer.h" +#include "nsTHashMap.h" +#include "nsDeque.h" +#include "nsIRunnable.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" + +#include "prmem.h" +#include "prio.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +LazyLogModule gFuzzingLog("nsFuzzingNecko"); + +#define FUZZING_LOG(args) \ + MOZ_LOG(mozilla::net::gFuzzingLog, mozilla::LogLevel::Verbose, args) + +// Mutex for modifying our hash tables +Mutex gConnRecvMutex("ConnectRecvMutex"); + +// This structure will be created by addNetworkFuzzingBuffer below +// and then added to the gNetworkFuzzingBuffers structure. +// +// It is assigned on connect and associated with the socket it belongs to. +typedef struct { + const uint8_t* buf; + size_t size; + bool allowRead; + bool allowUnused; + PRNetAddr* addr; +} NetworkFuzzingBuffer; + +// This holds all connections we have currently open. +static nsTHashMap, NetworkFuzzingBuffer*> + gConnectedNetworkFuzzingBuffers; + +// This holds all buffers for connections we can still open. +static nsDeque gNetworkFuzzingBuffers; + +// This is `true` once all connections are closed and either there are +// no buffers left to be used or all remaining buffers are marked optional. +// Used by `signalNetworkFuzzingDone` to tell the main thread if it needs +// to spin-wait for `gFuzzingConnClosed`. +static Atomic fuzzingNoWaitRequired(false); + +// Used to memorize if the main thread has indicated that it is done with +// its iteration and we don't expect more connections now. +static Atomic fuzzingMainSignaledDone(false); + +/* + * The flag `gFuzzingConnClosed` is set by `FuzzyClose` when all connections + * are closed *and* there are no more buffers in `gNetworkFuzzingBuffers` that + * must be used. The main thread spins until this becomes true to synchronize + * the fuzzing iteration between the main thread and the socket thread, if + * the prior call to `signalNetworkFuzzingDone` returned `false`. + */ +Atomic gFuzzingConnClosed(true); + +void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, bool readFirst, + bool useIsOptional) { + if (size > INT32_MAX) { + MOZ_CRASH("Unsupported buffer size"); + } + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* buf = new NetworkFuzzingBuffer(); + buf->buf = data; + buf->size = size; + buf->allowRead = readFirst; + buf->allowUnused = useIsOptional; + buf->addr = nullptr; + + gNetworkFuzzingBuffers.Push(buf); + + fuzzingMainSignaledDone = false; + fuzzingNoWaitRequired = false; +} + +/* + * This method should be called by fuzzing from the main thread to signal to + * the layer code that a fuzzing iteration is done. As a result, we can throw + * away any optional buffers and signal back once all connections have been + * closed. The main thread should synchronize on all connections being closed + * after the actual request/action is complete. + */ +bool signalNetworkFuzzingDone() { + FUZZING_LOG(("[signalNetworkFuzzingDone] Called.")); + MutexAutoLock lock(gConnRecvMutex); + bool rv = false; + + if (fuzzingNoWaitRequired) { + FUZZING_LOG(("[signalNetworkFuzzingDone] Purging remaining buffers.")); + // Easy case, we already have no connections and non-optional buffers left. + gNetworkFuzzingBuffers.Erase(); + gFuzzingConnClosed = true; + rv = true; + } else { + // We still have either connections left open or non-optional buffers left. + // In this case, FuzzyClose will handle the tear-down and signaling. + fuzzingMainSignaledDone = true; + } + + return rv; +} + +static PRDescIdentity sFuzzyLayerIdentity; +static PRIOMethods sFuzzyLayerMethods; +static PRIOMethods* sFuzzyLayerMethodsPtr = nullptr; + +static PRInt16 PR_CALLBACK FuzzyPoll(PRFileDesc* fd, PRInt16 in_flags, + PRInt16* out_flags) { + *out_flags = 0; + + FUZZING_LOG(("[FuzzyPoll] Called with in_flags=%X.", in_flags)); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + + if (in_flags & PR_POLL_READ && fuzzBuf && fuzzBuf->allowRead) { + *out_flags = PR_POLL_READ; + return PR_POLL_READ; + } + + if (in_flags & PR_POLL_WRITE) { + *out_flags = PR_POLL_WRITE; + return PR_POLL_WRITE; + } + + return in_flags; +} + +static PRStatus FuzzyConnect(PRFileDesc* fd, const PRNetAddr* addr, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront(); + if (!buf) { + FUZZING_LOG(("[FuzzyConnect] Denying additional connection.")); + return PR_FAILURE; + } + + gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf); + fuzzingNoWaitRequired = false; + + FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd)); + + gFuzzingConnClosed = false; + + return PR_SUCCESS; +} + +static PRInt32 FuzzySendTo(PRFileDesc* fd, const void* buf, PRInt32 amount, + PRIntn flags, const PRNetAddr* addr, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront(); + if (!buf) { + FUZZING_LOG(("[FuzzySentTo] Denying additional connection.")); + return 0; + } + + gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf); + + // Store connection address + buf->addr = new PRNetAddr; + memcpy(buf->addr, addr, sizeof(PRNetAddr)); + + fuzzingNoWaitRequired = false; + + FUZZING_LOG(("[FuzzySendTo] Successfully opened connection: %p", fd)); + + gFuzzingConnClosed = false; + } + + // Allow reading once the implementation has written at least some data + if (fuzzBuf && !fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzySendTo] Write received, allowing further reads.")); + fuzzBuf->allowRead = true; + } + + FUZZING_LOG(("[FuzzySendTo] Sent %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzySend(PRFileDesc* fd, const void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzySend] Write on socket that is not connected.")); + amount = 0; + } + + // Allow reading once the implementation has written at least some data + if (fuzzBuf && !fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzySend] Write received, allowing further reads.")); + fuzzBuf->allowRead = true; + } + + FUZZING_LOG(("[FuzzySend] Sent %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzyWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) { + return FuzzySend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed.")); + return 0; + } + + // As long as we haven't written anything, act as if no data was there yet + if (!fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzyRecv] Denying read, nothing written before.")); + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + if (gFuzzingConnClosed) { + FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed.")); + return 0; + } + + // No data left, act as if the connection was closed. + if (!fuzzBuf->size) { + FUZZING_LOG(("[FuzzyRecv] Read failed, no data left.")); + return 0; + } + + // Use the remains of fuzzing buffer, if too little is left + if (fuzzBuf->size < (PRUint32)amount) amount = fuzzBuf->size; + + // Consume buffer, copy data + memcpy(buf, fuzzBuf->buf, amount); + + if (!(flags & PR_MSG_PEEK)) { + fuzzBuf->buf += amount; + fuzzBuf->size -= amount; + } + + FUZZING_LOG(("[FuzzyRecv] Read %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzyRecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRNetAddr* addr, + PRIntervalTime timeout) { + // Return the same address used for initial SendTo on this fd + if (addr) { + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzyRecvFrom] Denying read, connection is closed.")); + return 0; + } + + if (fuzzBuf->addr) { + memcpy(addr, fuzzBuf->addr, sizeof(PRNetAddr)); + } else { + FUZZING_LOG(("[FuzzyRecvFrom] No address found for connection")); + } + } + return FuzzyRecv(fd, buf, amount, flags, timeout); +} + +static PRInt32 FuzzyRead(PRFileDesc* fd, void* buf, PRInt32 amount) { + return FuzzyRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRStatus FuzzyClose(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + MOZ_RELEASE_ASSERT(layer && layer->identity == sFuzzyLayerIdentity, + "Fuzzy Layer not on top of stack"); + + layer->dtor(layer); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = nullptr; + if (gConnectedNetworkFuzzingBuffers.Remove(fd, &fuzzBuf)) { + FUZZING_LOG(("[FuzzyClose] Received close on socket %p", fd)); + if (fuzzBuf->addr) { + delete fuzzBuf->addr; + } + delete fuzzBuf; + } else { + /* Happens when close is called on a non-connected socket */ + FUZZING_LOG(("[FuzzyClose] Received close on unknown socket %p.", fd)); + } + + PRStatus ret = fd->methods->close(fd); + + if (!gConnectedNetworkFuzzingBuffers.Count()) { + // At this point, all connections are closed, but we might still have + // unused network buffers that were not marked as optional. + bool haveRemainingUnusedBuffers = false; + for (size_t i = 0; i < gNetworkFuzzingBuffers.GetSize(); ++i) { + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.ObjectAt(i); + + if (!buf->allowUnused) { + haveRemainingUnusedBuffers = true; + break; + } + } + + if (haveRemainingUnusedBuffers) { + FUZZING_LOG( + ("[FuzzyClose] All connections closed, waiting for remaining " + "connections.")); + } else if (!fuzzingMainSignaledDone) { + // We have no connections left, but the main thread hasn't signaled us + // yet. For now, that means once the main thread signals us, we can tell + // it immediately that it won't have to wait for closing connections. + FUZZING_LOG( + ("[FuzzyClose] All connections closed, waiting for main thread.")); + fuzzingNoWaitRequired = true; + } else { + // No connections left and main thread is already done. Perform cleanup + // and then signal the main thread to continue. + FUZZING_LOG(("[FuzzyClose] All connections closed, cleaning up.")); + + gNetworkFuzzingBuffers.Erase(); + gFuzzingConnClosed = true; + + // We need to dispatch this so the main thread is guaranteed to wake up + nsCOMPtr r(new mozilla::Runnable("Dummy")); + NS_DispatchToMainThread(r.forget()); + } + } else { + FUZZING_LOG(("[FuzzyClose] Connection closed.")); + } + + return ret; +} + +nsresult AttachFuzzyIOLayer(PRFileDesc* fd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!sFuzzyLayerMethodsPtr) { + sFuzzyLayerIdentity = PR_GetUniqueIdentity("Fuzzy Layer"); + sFuzzyLayerMethods = *PR_GetDefaultIOMethods(); + sFuzzyLayerMethods.connect = FuzzyConnect; + sFuzzyLayerMethods.send = FuzzySend; + sFuzzyLayerMethods.sendto = FuzzySendTo; + sFuzzyLayerMethods.write = FuzzyWrite; + sFuzzyLayerMethods.recv = FuzzyRecv; + sFuzzyLayerMethods.recvfrom = FuzzyRecvFrom; + sFuzzyLayerMethods.read = FuzzyRead; + sFuzzyLayerMethods.close = FuzzyClose; + sFuzzyLayerMethods.poll = FuzzyPoll; + sFuzzyLayerMethodsPtr = &sFuzzyLayerMethods; + } + + PRFileDesc* layer = + PR_CreateIOLayerStub(sFuzzyLayerIdentity, sFuzzyLayerMethodsPtr); + + if (!layer) { + return NS_ERROR_FAILURE; + } + + PRStatus status = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/FuzzyLayer.h b/netwerk/base/FuzzyLayer.h new file mode 100644 index 0000000000..6b8109f949 --- /dev/null +++ b/netwerk/base/FuzzyLayer.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzyLayer_h__ +#define FuzzyLayer_h__ + +#include "prerror.h" +#include "nsError.h" +#include "nsIFile.h" + +namespace mozilla { +namespace net { + +nsresult AttachFuzzyIOLayer(PRFileDesc* fd); + +extern Atomic gFuzzingConnClosed; +bool signalNetworkFuzzingDone(); + +void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, + bool readFirst = false, + bool useIsOptional = false); + +} // namespace net +} // namespace mozilla + +#endif // FuzzyLayer_h__ diff --git a/netwerk/base/FuzzySecurityInfo.cpp b/netwerk/base/FuzzySecurityInfo.cpp new file mode 100644 index 0000000000..7a2405ab0a --- /dev/null +++ b/netwerk/base/FuzzySecurityInfo.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzySecurityInfo.h" + +#include "nsIWebProgressListener.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +FuzzySecurityInfo::FuzzySecurityInfo() {} + +FuzzySecurityInfo::~FuzzySecurityInfo() {} + +NS_IMPL_ISUPPORTS(FuzzySecurityInfo, nsITransportSecurityInfo) + +NS_IMETHODIMP +FuzzySecurityInfo::GetSecurityState(uint32_t* state) { + *state = nsIWebProgressListener::STATE_IS_SECURE; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetErrorCode(int32_t* state) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetErrorCodeString(nsAString& aErrorString) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetFailedCertChain( + nsTArray>& aFailedCertChain) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetServerCert(nsIX509Cert** aServerCert) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSucceededCertChain( + nsTArray>& aSucceededCertChain) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetCipherName(nsACString& aCipherName) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetKeyLength(uint32_t* aKeyLength) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSecretKeyLength(uint32_t* aSecretKeyLength) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetKeaGroupName(nsACString& aKeaGroup) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSignatureSchemeName(nsACString& aSignatureScheme) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetProtocolVersion(uint16_t* aProtocolVersion) { + NS_ENSURE_ARG_POINTER(aProtocolVersion); + // Must be >= TLS 1.2 for HTTP2 + *aProtocolVersion = nsITransportSecurityInfo::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetCertificateTransparencyStatus( + uint16_t* aCertificateTransparencyStatus) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsDelegatedCredential(bool* aIsDelegCred) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetOverridableErrorCategory( + OverridableErrorCategory* aOverridableErrorCode) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetMadeOCSPRequests(bool* aMadeOCSPRequests) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetUsedPrivateDNS(bool* aUsedPrivateDNS) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsExtendedValidation(bool* aIsEV) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::ToString(nsACString& aResult) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +void FuzzySecurityInfo::SerializeToIPC(IPC::MessageWriter* aWriter) { + MOZ_CRASH("Unused"); +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) { + aNegotiatedNPN.Assign("h2"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetResumed(bool* aResumed) { + *aResumed = false; + return NS_OK; +} + +NS_IMETHODIMP FuzzySecurityInfo::GetIsBuiltCertChainRootBuiltInRoot( + bool* aIsBuiltInRoot) { + *aIsBuiltInRoot = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetPeerId(nsACString& aResult) { + aResult.Assign(""_ns); + return NS_OK; +} + +} // namespace net + +} // namespace mozilla diff --git a/netwerk/base/FuzzySecurityInfo.h b/netwerk/base/FuzzySecurityInfo.h new file mode 100644 index 0000000000..121c6c8ba9 --- /dev/null +++ b/netwerk/base/FuzzySecurityInfo.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzySecurityInfo_h__ +#define FuzzySecurityInfo_h__ + +#include "nsCOMPtr.h" +#include "nsITransportSecurityInfo.h" + +namespace mozilla { +namespace net { + +class FuzzySecurityInfo final : public nsITransportSecurityInfo { + public: + FuzzySecurityInfo(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTSECURITYINFO + + protected: + virtual ~FuzzySecurityInfo(); +}; // class FuzzySecurityInfo + +} // namespace net +} // namespace mozilla + +#endif // FuzzySecurityInfo_h__ diff --git a/netwerk/base/FuzzySocketControl.cpp b/netwerk/base/FuzzySocketControl.cpp new file mode 100644 index 0000000000..ec24d1da6b --- /dev/null +++ b/netwerk/base/FuzzySocketControl.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzySocketControl.h" + +#include "FuzzySecurityInfo.h" +#include "ipc/IPCMessageUtils.h" +#include "nsITlsHandshakeListener.h" +#include "sslt.h" + +namespace mozilla { +namespace net { + +FuzzySocketControl::FuzzySocketControl() {} + +FuzzySocketControl::~FuzzySocketControl() {} + +NS_IMPL_ISUPPORTS(FuzzySocketControl, nsITLSSocketControl) + +NS_IMETHODIMP +FuzzySocketControl::GetProviderFlags(uint32_t* aProviderFlags) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetKEAUsed(int16_t* aKea) { + // Can be ssl_kea_dh or ssl_kea_ecdh for HTTP2 + *aKea = ssl_kea_ecdh; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetKEAKeyBits(uint32_t* aKeyBits) { + // Must be >= 224 for ecdh and >= 2048 for dh when using HTTP2 + *aKeyBits = 256; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetSSLVersionUsed(int16_t* aSSLVersionUsed) { + // Must be >= TLS 1.2 for HTTP2 + *aSSLVersionUsed = nsITLSSocketControl::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) { + *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetMACAlgorithmUsed(int16_t* aMac) { + // The only valid choice for HTTP2 is SSL_MAC_AEAD + *aMac = nsITLSSocketControl::SSL_MAC_AEAD; + return NS_OK; +} + +bool FuzzySocketControl::GetDenyClientCert() { return false; } + +void FuzzySocketControl::SetDenyClientCert(bool aDenyClientCert) { + // Called by mozilla::net::nsHttpConnection::StartSpdy +} + +NS_IMETHODIMP +FuzzySocketControl::GetClientCertSent(bool* arg) { + *arg = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetFailedVerification(bool* arg) { + *arg = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetAlpnEarlySelection(nsACString& aAlpnSelected) { + // TODO: For now we don't support early selection + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEarlyDataAccepted(bool* aAccepted) { + *aAccepted = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::DriveHandshake() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::IsAcceptableForHost(const nsACString& hostname, + bool* _retval) { + NS_ENSURE_ARG(_retval); + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::TestJoinConnection(const nsACString& npnProtocol, + const nsACString& hostname, int32_t port, + bool* _retval) { + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::JoinConnection(const nsACString& npnProtocol, + const nsACString& hostname, int32_t port, + bool* _retval) { + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::ProxyStartSSL() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::StartTLS() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetNPNList(nsTArray& protocolArray) { + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetEsniTxt(const nsACString& aEsniTxt) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEchConfig(nsACString& aEchConfig) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetEchConfig(const nsACString& aEchConfig) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetRetryEchConfig(nsACString& aEchConfig) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::GetPeerId(nsACString& aResult) { + aResult.Assign(""_ns); + return NS_OK; +} + +NS_IMETHODIMP FuzzySocketControl::SetHandshakeCallbackListener( + nsITlsHandshakeCallbackListener* callback) { + if (callback) { + callback->HandshakeDone(); + } + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::DisableEarlyData(void) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP FuzzySocketControl::GetSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + nsCOMPtr securityInfo(new FuzzySecurityInfo()); + securityInfo.forget(aSecurityInfo); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::AsyncGetSecurityInfo(JSContext* aCx, + mozilla::dom::Promise** aPromise) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP FuzzySocketControl::Claim() { return NS_OK; } + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/FuzzySocketControl.h b/netwerk/base/FuzzySocketControl.h new file mode 100644 index 0000000000..9debbe32e8 --- /dev/null +++ b/netwerk/base/FuzzySocketControl.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzySocketControl_h__ +#define FuzzySocketControl_h__ + +#include "nsITLSSocketControl.h" + +namespace mozilla { +namespace net { + +class FuzzySocketControl final : public nsITLSSocketControl { + public: + FuzzySocketControl(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSOCKETCONTROL + + protected: + virtual ~FuzzySocketControl(); +}; // class FuzzySocketControl + +} // namespace net +} // namespace mozilla + +#endif // FuzzySocketControl_h__ diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp new file mode 100644 index 0000000000..76047a7a00 --- /dev/null +++ b/netwerk/base/IOActivityMonitor.cpp @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "IOActivityMonitor.h" +#include "nsPrintfCString.h" +#include "nsSocketTransport2.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "prerror.h" +#include "prio.h" +#include "prmem.h" +#include + +using namespace mozilla; +using namespace mozilla::net; + +mozilla::StaticRefPtr gInstance; +static Atomic gActivated(false); +static PRDescIdentity sNetActivityMonitorLayerIdentity; +static PRIOMethods sNetActivityMonitorLayerMethods; +static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr; + +// Maximum number of activities entries in the monitoring class +#define MAX_ACTIVITY_ENTRIES 1000 + +// ActivityMonitorSecret is stored in the activity monitor layer +// and provides a method to get the location. +// +// A location can be : +// - a TCP or UDP socket. The form will be socket://ip:port +// - a File. The form will be file://path +// +// For other cases, the location will be fd://number +class ActivityMonitorSecret final { + public: + // constructor used for sockets + explicit ActivityMonitorSecret(PRFileDesc* aFd) { + mFd = aFd; + mLocationSet = false; + } + + // constructor used for files + explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) { + mFd = aFd; + mLocation.AppendPrintf("file://%s", aLocation); + mLocationSet = true; + } + + nsCString getLocation() { + if (!mLocationSet) { + LazySetLocation(); + } + return mLocation; + } + + private: + // Called to set the location using the FD on the first getLocation() usage + // which is typically when a socket is opened. If done earlier, at + // construction time, the host won't be bound yet. + // + // If the location is a file, it needs to be initialized in the + // constructor. + void LazySetLocation() { + mLocationSet = true; + PRFileDesc* extract = mFd; + while (PR_GetDescType(extract) == PR_DESC_LAYERED) { + if (!extract->lower) { + break; + } + extract = extract->lower; + } + + PRDescType fdType = PR_GetDescType(extract); + // we should not use LazySetLocation for files + MOZ_ASSERT(fdType != PR_DESC_FILE); + + switch (fdType) { + case PR_DESC_SOCKET_TCP: + case PR_DESC_SOCKET_UDP: { + mLocation.AppendPrintf("socket://"); + PRNetAddr addr; + PRStatus status = PR_GetSockName(mFd, &addr); + if (NS_WARN_IF(status == PR_FAILURE)) { + mLocation.AppendPrintf("unknown"); + break; + } + + // grabbing the host + char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0}; + status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1); + if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) { + mLocation.AppendPrintf("unknown"); + break; + } + mLocation.Append(netAddr); + + // adding the port + uint16_t port; + if (addr.raw.family == PR_AF_INET) { + port = addr.inet.port; + } else { + port = addr.ipv6.port; + } + mLocation.AppendPrintf(":%d", port); + } break; + + // for all other cases, we just send back fd:// + default: { + mLocation.AppendLiteral("fd://"); + mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd)); + } + } // end switch + } + + private: + nsCString mLocation; + bool mLocationSet; + PRFileDesc* mFd; +}; + +// FileDesc2Location converts a PRFileDesc into a "location" by +// grabbing the ActivityMonitorSecret in layer->secret +static nsAutoCString FileDesc2Location(PRFileDesc* fd) { + nsAutoCString location; + PRFileDesc* monitorLayer = + PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity); + if (!monitorLayer) { + location.AppendPrintf("unknown"); + return location; + } + + ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret; + location.AppendPrintf("%s", secret->getLocation().get()); + return location; +} + +// +// Wrappers around the socket APIS +// +static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr, + PRIntervalTime timeout) { + return fd->lower->methods->connect(fd->lower, addr, timeout); +} + +static PRStatus nsNetMon_Close(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + MOZ_RELEASE_ASSERT( + layer && layer->identity == sNetActivityMonitorLayerIdentity, + "NetActivityMonitor Layer not on top of stack"); + + if (layer->secret) { + delete (ActivityMonitorSecret*)layer->secret; + layer->secret = nullptr; + } + layer->dtor(layer); + return fd->methods->close(fd); +} + +static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) { + int32_t ret = fd->lower->methods->read(fd->lower, buf, len); + if (ret >= 0) { + IOActivityMonitor::Read(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) { + int32_t ret = fd->lower->methods->write(fd->lower, buf, len); + if (ret > 0) { + IOActivityMonitor::Write(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, size); + } + return ret; +} + +static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags, + addr, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, const PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock, + PRFileDesc** acceptedSock, + PRNetAddr** peerAddr, void* buf, + int32_t amount, PRIntervalTime timeout) { + int32_t ret = listenSock->lower->methods->acceptread( + listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout); + if (ret > 0) { + IOActivityMonitor::Read(listenSock, amount); + } + return ret; +} + +// +// Class IOActivityMonitor +// +NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed) + +IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") { + RefPtr mon(gInstance); + MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!"); +} + +// static +void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) { + MOZ_ASSERT(aPromise); + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + aPromise->MaybeReject(NS_ERROR_FAILURE); + return; + } + + mon->RequestActivitiesInternal(aPromise); +} + +void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) { + nsresult result = NS_OK; + FallibleTArray activities; + + { + mozilla::MutexAutoLock lock(mLock); + // Remove inactive activities + for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) { + dom::IOActivityDataDictionary* activity = &iter.Data(); + if (activity->mRx == 0 && activity->mTx == 0) { + iter.Remove(); + } else { + if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) { + result = NS_ERROR_OUT_OF_MEMORY; + break; + } + } + } + } + + if (NS_WARN_IF(NS_FAILED(result))) { + aPromise->MaybeReject(result); + return; + } + aPromise->MaybeResolve(activities); +} + +NS_IMETHODIMP +IOActivityMonitor::GetName(nsACString& aName) { + aName.AssignLiteral("IOActivityMonitor"); + return NS_OK; +} + +// static +bool IOActivityMonitor::IsActive() { return gActivated; } + +// static +already_AddRefed IOActivityMonitor::Get() { + if (!gActivated) { + return nullptr; + } + + RefPtr mon = gInstance; + return mon.forget(); +} + +nsresult IOActivityMonitor::Init() { + if (IsActive()) { + return NS_ERROR_ALREADY_INITIALIZED; + } + RefPtr mon = new IOActivityMonitor(); + nsresult rv = mon->InitInternal(); + if (NS_SUCCEEDED(rv)) { + gInstance = mon; + ClearOnShutdown(&gInstance); + gActivated = true; + } + return rv; +} + +nsresult IOActivityMonitor::InitInternal() { + // wraps the socket APIs + if (!sNetActivityMonitorLayerMethodsPtr) { + sNetActivityMonitorLayerIdentity = + PR_GetUniqueIdentity("network activity monitor layer"); + sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods(); + sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect; + sNetActivityMonitorLayerMethods.read = nsNetMon_Read; + sNetActivityMonitorLayerMethods.write = nsNetMon_Write; + sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev; + sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv; + sNetActivityMonitorLayerMethods.send = nsNetMon_Send; + sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom; + sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo; + sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead; + sNetActivityMonitorLayerMethods.close = nsNetMon_Close; + sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods; + } + + return NS_OK; +} + +nsresult IOActivityMonitor::Shutdown() { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_NOT_INITIALIZED; + } + return mon->ShutdownInternal(); +} + +nsresult IOActivityMonitor::ShutdownInternal() { + mozilla::MutexAutoLock lock(mLock); + mActivities.Clear(); + gActivated = false; + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd); + layer->secret = reinterpret_cast(secret); + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath); + layer->secret = reinterpret_cast(secret); + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation, + uint32_t aRx, uint32_t aTx) { + mLock.AssertCurrentThreadOwns(); + return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) { + if (!entry) return false; + + if (*entry) { + // already registered + entry->Data().mTx += aTx; + entry->Data().mRx += aRx; + } else { + // Creating a new IOActivity. Notice that mActivities + // will grow indefinitely, which is OK since we won't + // have but a few hundreds entries at the most, but we + // want to assert we have at the most 1000 entries + MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES); + + dom::IOActivityDataDictionary activity; + activity.mLocation.Assign(aLocation); + activity.mTx = aTx; + activity.mRx = aRx; + + entry->Insert(std::move(activity)); + } + + return true; + }); +} + +nsresult IOActivityMonitor::Write(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->WriteInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Write(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, aAmount, 0)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Read(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::Read(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->ReadInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, 0, aAmount)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/IOActivityMonitor.h b/netwerk/base/IOActivityMonitor.h new file mode 100644 index 0000000000..5e46bc9bfc --- /dev/null +++ b/netwerk/base/IOActivityMonitor.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IOActivityMonitor_h___ +#define IOActivityMonitor_h___ + +#include "mozilla/dom/ChromeUtilsBinding.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsINamed.h" +#include "nsISupports.h" +#include "prinrval.h" +#include "prio.h" +#include "private/pprio.h" +#include + +namespace mozilla { + +namespace dom { +class Promise; +} + +namespace net { + +#define IO_ACTIVITY_ENABLED_PREF "io.activity.enabled" + +using Activities = nsTHashMap; + +// IOActivityMonitor has several roles: +// - maintains an IOActivity per resource and updates it +// - sends a dump of the activities to a promise via RequestActivities +class IOActivityMonitor final : public nsINamed { + public: + IOActivityMonitor(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINAMED + + // initializes and destroys the singleton + static nsresult Init(); + static nsresult Shutdown(); + + // collect amounts of data that are written/read by location + static nsresult Read(const nsACString& location, uint32_t aAmount); + static nsresult Write(const nsACString& location, uint32_t aAmount); + + static nsresult MonitorFile(PRFileDesc* aFd, const char* aPath); + static nsresult MonitorSocket(PRFileDesc* aFd); + static nsresult Read(PRFileDesc* fd, uint32_t aAmount); + static nsresult Write(PRFileDesc* fd, uint32_t aAmount); + + static bool IsActive(); + static void RequestActivities(dom::Promise* aPromise); + + private: + ~IOActivityMonitor() = default; + + static already_AddRefed Get(); + + nsresult InitInternal(); + nsresult ShutdownInternal(); + bool IncrementActivity(const nsACString& location, uint32_t aRx, + uint32_t aTx); + nsresult WriteInternal(const nsACString& location, uint32_t aAmount); + nsresult ReadInternal(const nsACString& location, uint32_t aAmount); + void RequestActivitiesInternal(dom::Promise* aPromise); + + Activities mActivities; + // protects mActivities accesses + Mutex mLock MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif /* IOActivityMonitor_h___ */ diff --git a/netwerk/base/IPv6Utils.h b/netwerk/base/IPv6Utils.h new file mode 100644 index 0000000000..437c0befec --- /dev/null +++ b/netwerk/base/IPv6Utils.h @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NETWORK_IPV6_UTILS_H_ +#define NETWORK_IPV6_UTILS_H_ + +namespace mozilla { +namespace net { +namespace utils { + +// IPv6 address scopes. +#define IPV6_SCOPE_GLOBAL 0 // Global scope. +#define IPV6_SCOPE_LINKLOCAL 1 // Link-local scope. +#define IPV6_SCOPE_SITELOCAL 2 // Site-local scope (deprecated). +#define IPV6_SCOPE_UNIQUELOCAL 3 // Unique local +#define IPV6_SCOPE_NODELOCAL 4 // Loopback + +// Return the scope of the given address. +static int ipv6_scope(const unsigned char addr[16]) { + const unsigned char* b = addr; + unsigned short w = (unsigned short)((b[0] << 8) | b[1]); + + if ((b[0] & 0xFE) == 0xFC) { + return IPV6_SCOPE_UNIQUELOCAL; + } + switch (w & 0xFFC0) { + case 0xFE80: + return IPV6_SCOPE_LINKLOCAL; + case 0xFEC0: + return IPV6_SCOPE_SITELOCAL; + case 0x0000: + w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | b[10] | + b[11] | b[12] | b[13] | b[14]; + if (w || b[15] != 0x01) { + break; + } + return IPV6_SCOPE_NODELOCAL; + default: + break; + } + + return IPV6_SCOPE_GLOBAL; +} + +} // namespace utils +} // namespace net +} // namespace mozilla + +#endif // NETWORK_IPV6_UTILS_H_ diff --git a/netwerk/base/InterceptionInfo.cpp b/netwerk/base/InterceptionInfo.cpp new file mode 100644 index 0000000000..977ec02e47 --- /dev/null +++ b/netwerk/base/InterceptionInfo.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/InterceptionInfo.h" +#include "nsContentUtils.h" + +namespace mozilla::net { + +NS_IMPL_ISUPPORTS(InterceptionInfo, nsIInterceptionInfo) + +InterceptionInfo::InterceptionInfo(nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, + const RedirectHistoryArray& aRedirectChain, + bool aFromThirdParty) + : mTriggeringPrincipal(aTriggeringPrincipal), + mContentPolicyType(aContentPolicyType), + mFromThirdParty(aFromThirdParty) { + SetRedirectChain(aRedirectChain); +} + +nsIPrincipal* InterceptionInfo::TriggeringPrincipal() { + return mTriggeringPrincipal; +} + +void InterceptionInfo::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) { + mTriggeringPrincipal = aPrincipal; +} + +nsContentPolicyType InterceptionInfo::ContentPolicyType() { + return mContentPolicyType; +} + +nsContentPolicyType InterceptionInfo::ExternalContentPolicyType() { + return static_cast( + nsContentUtils::InternalContentPolicyTypeToExternal(mContentPolicyType)); +} + +void InterceptionInfo::SetContentPolicyType( + const nsContentPolicyType aContentPolicyType) { + mContentPolicyType = aContentPolicyType; +} + +const RedirectHistoryArray& InterceptionInfo::RedirectChain() { + return mRedirectChain; +} + +void InterceptionInfo::SetRedirectChain( + const RedirectHistoryArray& aRedirectChain) { + for (auto entry : aRedirectChain) { + mRedirectChain.AppendElement(entry); + } +} + +bool InterceptionInfo::FromThirdParty() { return mFromThirdParty; } + +void InterceptionInfo::SetFromThirdParty(bool aFromThirdParty) { + mFromThirdParty = aFromThirdParty; +} + +} // namespace mozilla::net diff --git a/netwerk/base/InterceptionInfo.h b/netwerk/base/InterceptionInfo.h new file mode 100644 index 0000000000..ad2f2a4499 --- /dev/null +++ b/netwerk/base/InterceptionInfo.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_InterceptionInfo_h +#define mozilla_net_InterceptionInfo_h + +#include "nsIContentSecurityPolicy.h" +#include "nsIInterceptionInfo.h" +#include "nsIPrincipal.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" + +namespace mozilla::net { + +using RedirectHistoryArray = nsTArray>; + +class InterceptionInfo final : public nsIInterceptionInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERCEPTIONINFO + + InterceptionInfo(nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, + const RedirectHistoryArray& aRedirectChain, + bool aFromThirdParty); + + using nsIInterceptionInfo::GetExtContentPolicyType; + + private: + ~InterceptionInfo() = default; + + nsCOMPtr mTriggeringPrincipal; + nsContentPolicyType mContentPolicyType{nsIContentPolicy::TYPE_INVALID}; + RedirectHistoryArray mRedirectChain; + bool mFromThirdParty = false; +}; + +} // namespace mozilla::net + +#endif diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp new file mode 100644 index 0000000000..d544bf7275 --- /dev/null +++ b/netwerk/base/LoadContextInfo.cpp @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LoadContextInfo.h" + +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "nsDocShell.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIWebNavigation.h" +#include "nsNetUtil.h" + +using namespace mozilla::dom; +namespace mozilla { +namespace net { + +// LoadContextInfo + +NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo) + +LoadContextInfo::LoadContextInfo(bool aIsAnonymous, + OriginAttributes aOriginAttributes) + : mIsAnonymous(aIsAnonymous), + mOriginAttributes(std::move(aOriginAttributes)) {} + +NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool* aIsPrivate) { + *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool* aIsAnonymous) { + *aIsAnonymous = mIsAnonymous; + return NS_OK; +} + +OriginAttributes const* LoadContextInfo::OriginAttributesPtr() { + return &mOriginAttributes; +} + +NS_IMETHODIMP LoadContextInfo::GetOriginAttributes( + JSContext* aCx, JS::MutableHandle 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 info = + GetLoadContextInfo(false, OriginAttributes()); + info.forget(aDefault); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetPrivate( + nsILoadContextInfo** aPrivate) { + OriginAttributes attrs; + attrs.SyncAttributesWithPrivateBrowsing(true); + nsCOMPtr info = GetLoadContextInfo(false, attrs); + info.forget(aPrivate); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous( + nsILoadContextInfo** aAnonymous) { + nsCOMPtr info = + GetLoadContextInfo(true, OriginAttributes()); + info.forget(aAnonymous); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::Custom( + bool aAnonymous, JS::Handle aOriginAttributes, JSContext* cx, + nsILoadContextInfo** _retval) { + OriginAttributes attrs; + bool status = attrs.Init(cx, aOriginAttributes); + NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); + + nsCOMPtr info = GetLoadContextInfo(aAnonymous, attrs); + info.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext( + nsILoadContext* aLoadContext, bool aAnonymous, + nsILoadContextInfo** _retval) { + nsCOMPtr info = + GetLoadContextInfo(aLoadContext, aAnonymous); + info.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow* aWindow, + bool aAnonymous, + nsILoadContextInfo** _retval) { + nsCOMPtr info = GetLoadContextInfo(aWindow, aAnonymous); + info.forget(_retval); + return NS_OK; +} + +// Helper functions + +LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel) { + nsresult rv; + + DebugOnly pb = NS_UsePrivateBrowsing(aChannel); + + bool anon = false; + nsLoadFlags loadFlags; + rv = aChannel->GetLoadFlags(&loadFlags); + if (NS_SUCCEEDED(rv)) { + anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS); + } + + OriginAttributes oa; + StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChannel, oa); + MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0)); + + return new LoadContextInfo(anon, oa); +} + +LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext, + bool aIsAnonymous) { + if (!aLoadContext) { + return new LoadContextInfo(aIsAnonymous, OriginAttributes()); + } + + OriginAttributes oa; + aLoadContext->GetOriginAttributes(oa); + +#ifdef DEBUG + nsCOMPtr docShell = do_QueryInterface(aLoadContext); + if (!docShell || + nsDocShell::Cast(docShell)->GetBrowsingContext()->IsContent()) { + MOZ_ASSERT(aLoadContext->UsePrivateBrowsing() == + (oa.mPrivateBrowsingId > 0)); + } +#endif + + return new LoadContextInfo(aIsAnonymous, oa); +} + +LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous) { + nsCOMPtr webNav = do_GetInterface(aWindow); + nsCOMPtr loadContext = do_QueryInterface(webNav); + + return GetLoadContextInfo(loadContext, aIsAnonymous); +} + +LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo) { + return new LoadContextInfo(aInfo->IsAnonymous(), + *aInfo->OriginAttributesPtr()); +} + +LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous, + OriginAttributes const& aOriginAttributes) { + return new LoadContextInfo(aIsAnonymous, aOriginAttributes); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h new file mode 100644 index 0000000000..efaf912db4 --- /dev/null +++ b/netwerk/base/LoadContextInfo.h @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsLoadContextInfo_h__ +#define nsLoadContextInfo_h__ + +#include "nsILoadContextInfo.h" + +class nsIChannel; +class nsILoadContext; + +namespace mozilla { +namespace net { + +class LoadContextInfo final : public nsILoadContextInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADCONTEXTINFO + + LoadContextInfo(bool aIsAnonymous, OriginAttributes aOriginAttributes); + + private: + virtual ~LoadContextInfo() = default; + + protected: + bool mIsAnonymous : 1; + OriginAttributes mOriginAttributes; +}; + +class LoadContextInfoFactory : public nsILoadContextInfoFactory { + virtual ~LoadContextInfoFactory() = default; + + public: + NS_DECL_ISUPPORTS // deliberately not thread-safe + NS_DECL_NSILOADCONTEXTINFOFACTORY +}; + +LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel); + +LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous); + +LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo); + +LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous, + OriginAttributes const& aOriginAttributes); + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp new file mode 100644 index 0000000000..e840a708fd --- /dev/null +++ b/netwerk/base/LoadInfo.cpp @@ -0,0 +1,2282 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/LoadInfo.h" + +#include "js/Array.h" // JS::NewArrayObject +#include "js/PropertyAndElement.h" // JS_DefineElement +#include "mozilla/Assertions.h" +#include "mozilla/ExpandedPrincipal.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozIThirdPartyUtil.h" +#include "ThirdPartyUtil.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocShell.h" +#include "mozilla/dom/Document.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIScriptElement.h" +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" +#include "nsIXPConnect.h" +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsMixedContentBlocker.h" +#include "nsQueryObject.h" +#include "nsRedirectHistoryEntry.h" +#include "nsSandboxFlags.h" +#include "nsICookieService.h" + +using namespace mozilla::dom; + +namespace mozilla::net { + +static nsCString CurrentRemoteType() { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsContentProcess()); + if (ContentChild* cc = ContentChild::GetSingleton()) { + return nsCString(cc->GetRemoteType()); + } + return NOT_REMOTE_TYPE; +} + +static nsContentPolicyType InternalContentPolicyTypeForFrame( + CanonicalBrowsingContext* aBrowsingContext) { + const auto& maybeEmbedderElementType = + aBrowsingContext->GetEmbedderElementType(); + MOZ_ASSERT(maybeEmbedderElementType.isSome()); + auto embedderElementType = maybeEmbedderElementType.value(); + + // Assign same type as in nsDocShell::DetermineContentType. + // N.B. internal content policy type will never be TYPE_DOCUMENT + return embedderElementType.EqualsLiteral("iframe") + ? nsIContentPolicy::TYPE_INTERNAL_IFRAME + : nsIContentPolicy::TYPE_INTERNAL_FRAME; +} + +/* static */ already_AddRefed LoadInfo::CreateForDocument( + dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags) { + return MakeAndAddRef(aBrowsingContext, aURI, aTriggeringPrincipal, + aTriggeringRemoteType, aOriginAttributes, + aSecurityFlags, aSandboxFlags); +} + +/* static */ already_AddRefed LoadInfo::CreateForFrame( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) { + return MakeAndAddRef(aBrowsingContext, aTriggeringPrincipal, + aTriggeringRemoteType, aSecurityFlags, + aSandboxFlags); +} + +/* static */ already_AddRefed LoadInfo::CreateForNonDocument( + dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags) { + return MakeAndAddRef( + aParentWGP, aTriggeringPrincipal, aParentWGP->GetRemoteType(), + aContentPolicyType, aSecurityFlags, aSandboxFlags); +} + +LoadInfo::LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const Maybe& aLoadingClientInfo, + const Maybe& aController, + uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized) + : mLoadingPrincipal(aLoadingContext ? aLoadingContext->NodePrincipal() + : aLoadingPrincipal), + mTriggeringPrincipal(aTriggeringPrincipal ? aTriggeringPrincipal + : mLoadingPrincipal.get()), + mTriggeringRemoteType(CurrentRemoteType()), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mClientInfo(aLoadingClientInfo), + mController(aController), + mLoadingContext(do_GetWeakReference(aLoadingContext)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(aContentPolicyType), + mSkipCheckForBrokenURLOrZeroSized(aSkipCheckForBrokenURLOrZeroSized) { + MOZ_ASSERT(mLoadingPrincipal); + MOZ_ASSERT(mTriggeringPrincipal); + +#ifdef DEBUG + // TYPE_DOCUMENT loads initiated by javascript tests will go through + // nsIOService and use the wrong constructor. Don't enforce the + // !TYPE_DOCUMENT check in those cases + bool skipContentTypeCheck = false; + skipContentTypeCheck = + Preferences::GetBool("network.loadinfo.skip_type_assertion"); +#endif + + // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't + // have a loadingPrincipal + MOZ_ASSERT(skipContentTypeCheck || mLoadingPrincipal || + mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT); + + // We should only get an explicit controller for subresource requests. + MOZ_DIAGNOSTIC_ASSERT(aController.isNothing() || + !nsContentUtils::IsNonSubresourceInternalPolicyType( + mInternalContentPolicyType)); + + // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false + // meaning that consumers of LoadInfo that don't pass a context or pass a + // context from which we can't find a window will default to assuming that + // they're 1st party. It would be nice if we could default "safe" and assume + // that we are 3rd party until proven otherwise. + + // if consumers pass both, aLoadingContext and aLoadingPrincipal + // then the loadingPrincipal must be the same as the node's principal + MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal || + aLoadingContext->NodePrincipal() == aLoadingPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + ExtContentPolicyType externalType = + nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType); + + if (aLoadingContext) { + // Ensure that all network requests for a window client have the ClientInfo + // properly set. Workers must currently pass the loading ClientInfo + // explicitly. We allow main thread requests to explicitly pass the value as + // well. + if (mClientInfo.isNothing()) { + mClientInfo = aLoadingContext->OwnerDoc()->GetClientInfo(); + } + + // For subresource loads set the service worker based on the calling + // context's controller. Workers must currently pass the controller in + // explicitly. We allow main thread requests to explicitly pass the value + // as well, but otherwise extract from the loading context here. + if (mController.isNothing() && + !nsContentUtils::IsNonSubresourceInternalPolicyType( + mInternalContentPolicyType)) { + mController = aLoadingContext->OwnerDoc()->GetController(); + } + + nsCOMPtr contextOuter = + aLoadingContext->OwnerDoc()->GetWindow(); + if (contextOuter) { + ComputeIsThirdPartyContext(contextOuter); + RefPtr bc = contextOuter->GetBrowsingContext(); + MOZ_ASSERT(bc); + mBrowsingContextID = bc->Id(); + + nsGlobalWindowInner* innerWindow = + nsGlobalWindowInner::Cast(contextOuter->GetCurrentInnerWindow()); + if (innerWindow) { + mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal(); + + if (!mTopLevelPrincipal && + externalType == ExtContentPolicy::TYPE_SUBDOCUMENT && bc->IsTop()) { + // If this is the first level iframe, innerWindow is our top-level + // principal. + mTopLevelPrincipal = innerWindow->GetPrincipal(); + } + } + + // Let's inherit the cookie behavior and permission from the parent + // document. + mCookieJarSettings = aLoadingContext->OwnerDoc()->CookieJarSettings(); + } + + mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID(); + RefPtr ctx = WindowContext::GetById(mInnerWindowID); + if (ctx) { + mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy(); + } + mDocumentHasUserInteracted = + aLoadingContext->OwnerDoc()->UserHasInteracted(); + + // Inherit HTTPS-Only Mode flags from parent document + mHttpsOnlyStatus |= aLoadingContext->OwnerDoc()->HttpsOnlyStatus(); + + // When the element being loaded is a frame, we choose the frame's window + // for the window ID and the frame element's window as the parent + // window. This is the behavior that Chrome exposes to add-ons. + // NB: If the frameLoaderOwner doesn't have a frame loader, then the load + // must be coming from an object (such as a plugin) that's loaded into it + // instead of a document being loaded. In that case, treat this object like + // any other non-document-loading element. + RefPtr frameLoaderOwner = + do_QueryObject(aLoadingContext); + RefPtr fl = + frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr; + if (fl) { + nsCOMPtr docShell = fl->GetDocShell(IgnoreErrors()); + if (docShell) { + nsCOMPtr outerWindow = do_GetInterface(docShell); + if (outerWindow) { + RefPtr bc = outerWindow->GetBrowsingContext(); + mFrameBrowsingContextID = bc ? bc->Id() : 0; + } + } + } + + // if the document forces all mixed content to be blocked, then we + // store that bit for all requests on the loadinfo. + mBlockAllMixedContent = + aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(false) || + (nsContentUtils::IsPreloadType(mInternalContentPolicyType) && + aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(true)); + + if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mLoadingPrincipal)) { + // if the load is triggered by an addon which potentially overrides the + // CSP of the document, then do not force insecure requests to be + // upgraded. + mUpgradeInsecureRequests = false; + } else { + // if the document forces all requests to be upgraded from http to https, + // then we should do that for all requests. If it only forces preloads to + // be upgraded then we should enforce upgrade insecure requests only for + // preloads. + mUpgradeInsecureRequests = + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) || + (nsContentUtils::IsPreloadType(mInternalContentPolicyType) && + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true)); + } + + if (nsMixedContentBlocker::IsUpgradableContentType( + mInternalContentPolicyType)) { + if (mLoadingPrincipal->SchemeIs("https")) { + if (StaticPrefs::security_mixed_content_upgrade_display_content()) { + mBrowserUpgradeInsecureRequests = true; + } else { + mBrowserWouldUpgradeInsecureRequests = true; + } + } + } + } + mOriginAttributes = mLoadingPrincipal->OriginAttributesRef(); + + // We need to do this after inheriting the document's origin attributes + // above, in case the loading principal ends up being the system principal. + if (aLoadingContext) { + nsCOMPtr loadContext = + aLoadingContext->OwnerDoc()->GetLoadContext(); + nsCOMPtr docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (loadContext && docShell && + docShell->GetBrowsingContext()->IsContent()) { + 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 docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (docShell) { + if (docShell->GetBrowsingContext()->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + } + } + + // in case this is a loadinfo for a parser generated script, then we store + // that bit of information so CSP strict-dynamic can query it. + if (!nsContentUtils::IsPreloadType(mInternalContentPolicyType)) { + nsCOMPtr script = do_QueryInterface(aLoadingContext); + if (script && script->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER) { + mParserCreatedScript = true; + } + } +} + +/* Constructor takes an outer window, but no loadingNode or loadingPrincipal. + * This constructor should only be used for TYPE_DOCUMENT loads, since they + * have a null loadingNode and loadingPrincipal. + */ +LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + nsISupports* aContextForTopLevelLoad, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(CurrentRemoteType()), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party + // Grab the information we can out of the window. + MOZ_ASSERT(aOuterWindow); + MOZ_ASSERT(mTriggeringPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + RefPtr bc = aOuterWindow->GetBrowsingContext(); + mBrowsingContextID = bc ? bc->Id() : 0; + + // This should be removed in bug 1618557 + nsGlobalWindowInner* innerWindow = + nsGlobalWindowInner::Cast(aOuterWindow->GetCurrentInnerWindow()); + if (innerWindow) { + mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal(); + } + + // get the docshell from the outerwindow, and then get the originattributes + nsCOMPtr docShell = aOuterWindow->GetDocShell(); + MOZ_ASSERT(docShell); + mOriginAttributes = nsDocShell::Cast(docShell)->GetOriginAttributes(); + + // We sometimes use this constructor for security checks for outer windows + // that aren't top level. + if (aSecurityFlags != nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK) { + MOZ_ASSERT(aOuterWindow->GetBrowsingContext()->IsTop()); + } + +#ifdef DEBUG + if (docShell->GetBrowsingContext()->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } +#endif + + // Let's take the current cookie behavior and current cookie permission + // for the documents' loadInfo. Note that for any other loadInfos, + // cookieBehavior will be BEHAVIOR_REJECT for security reasons. + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + aURI, mOriginAttributes, + "We are creating CookieJarSettings, so we can't have one already."); + mCookieJarSettings = CookieJarSettings::Create( + isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular, + shouldResistFingerprinting); +} + +LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party + // Grab the information we can out of the window. + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(mTriggeringPrincipal); + MOZ_ASSERT(aSecurityFlags != + nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + mBrowsingContextID = aBrowsingContext->Id(); + mOriginAttributes = aOriginAttributes; + +#ifdef DEBUG + if (aBrowsingContext->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } +#endif + + // If we think we should not resist fingerprinting, defer to the opener's + // RFP bit (if there is an opener.) If the opener is also exempted, it stays + // true, otherwise we will put a false into the CJS and that will be respected + // on this document. + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + aURI, mOriginAttributes, + "We are creating CookieJarSettings, so we can't have one already."); + + RefPtr opener = aBrowsingContext->GetOpener(); + if (!shouldResistFingerprinting && opener && + opener->GetCurrentWindowContext()) { + shouldResistFingerprinting = + opener->GetCurrentWindowContext()->ShouldResistFingerprinting(); + } + + const bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + + // Let's take the current cookie behavior and current cookie permission + // for the documents' loadInfo. Note that for any other loadInfos, + // cookieBehavior will be BEHAVIOR_REJECT for security reasons. + mCookieJarSettings = CookieJarSettings::Create( + isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular, + shouldResistFingerprinting); +} + +LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsContentPolicyType aContentPolicyType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(aContentPolicyType) { + CanonicalBrowsingContext* parentBC = aParentWGP->BrowsingContext(); + MOZ_ASSERT(parentBC); + ComputeAncestors(parentBC, mAncestorPrincipals, mAncestorBrowsingContextIDs); + + RefPtr topLevelWGP = aParentWGP->TopWindowContext(); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + // Ensure that all network requests for a window client have the ClientInfo + // properly set. + mClientInfo = aParentWGP->GetClientInfo(); + mLoadingPrincipal = aParentWGP->DocumentPrincipal(); + ComputeIsThirdPartyContext(aParentWGP); + + mBrowsingContextID = parentBC->Id(); + + // Let's inherit the cookie behavior and permission from the embedder + // document. + mCookieJarSettings = aParentWGP->CookieJarSettings(); + if (topLevelWGP->BrowsingContext()->IsTop()) { + if (mCookieJarSettings) { + bool stopAtOurLevel = mCookieJarSettings->GetCookieBehavior() == + nsICookieService::BEHAVIOR_REJECT_TRACKER; + if (!stopAtOurLevel || + topLevelWGP->OuterWindowId() != aParentWGP->OuterWindowId()) { + mTopLevelPrincipal = topLevelWGP->DocumentPrincipal(); + } + } + } + + if (!mTopLevelPrincipal && parentBC->IsTop()) { + // If this is the first level iframe, embedder WindowGlobalParent's document + // principal is our top-level principal. + mTopLevelPrincipal = aParentWGP->DocumentPrincipal(); + } + + mInnerWindowID = aParentWGP->InnerWindowId(); + mDocumentHasUserInteracted = aParentWGP->DocumentHasUserInteracted(); + + // if the document forces all mixed content to be blocked, then we + // store that bit for all requests on the loadinfo. + mBlockAllMixedContent = aParentWGP->GetDocumentBlockAllMixedContent(); + + if (mTopLevelPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mTopLevelPrincipal)) { + // if the load is triggered by an addon which potentially overrides the + // CSP of the document, then do not force insecure requests to be + // upgraded. + mUpgradeInsecureRequests = false; + } else { + // if the document forces all requests to be upgraded from http to https, + // then we should do that for all requests. If it only forces preloads to + // be upgraded then we should enforce upgrade insecure requests only for + // preloads. + mUpgradeInsecureRequests = aParentWGP->GetDocumentUpgradeInsecureRequests(); + } + mOriginAttributes = mLoadingPrincipal->OriginAttributesRef(); + + // We need to do this after inheriting the document's origin attributes + // above, in case the loading principal ends up being the system principal. + if (parentBC->IsContent()) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing( + parentBC->UsePrivateBrowsing()); + } + + mHttpsOnlyStatus |= aParentWGP->HttpsOnlyStatus(); + + // For chrome BC, the mPrivateBrowsingId remains 0 even its + // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in + // origin attributes if the type of the BC is content. + if (parentBC->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + + RefPtr ctx = WindowContext::GetById(mInnerWindowID); + if (ctx) { + mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy(); + + if (Document* document = ctx->GetDocument()) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + document->Trials().IsEnabled(OriginTrial::CoepCredentialless); + } + } +} + +// Used for TYPE_FRAME or TYPE_IFRAME load. +LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : LoadInfo(aBrowsingContext->GetParentWindowContext(), aTriggeringPrincipal, + aTriggeringRemoteType, + InternalContentPolicyTypeForFrame(aBrowsingContext), + aSecurityFlags, aSandboxFlags) { + mFrameBrowsingContextID = aBrowsingContext->Id(); +} + +LoadInfo::LoadInfo(const LoadInfo& rhs) + : mLoadingPrincipal(rhs.mLoadingPrincipal), + mTriggeringPrincipal(rhs.mTriggeringPrincipal), + mPrincipalToInherit(rhs.mPrincipalToInherit), + mTopLevelPrincipal(rhs.mTopLevelPrincipal), + mResultPrincipalURI(rhs.mResultPrincipalURI), + mChannelCreationOriginalURI(rhs.mChannelCreationOriginalURI), + mCookieJarSettings(rhs.mCookieJarSettings), + mCspToInherit(rhs.mCspToInherit), + mTriggeringRemoteType(rhs.mTriggeringRemoteType), + mSandboxedNullPrincipalID(rhs.mSandboxedNullPrincipalID), + mClientInfo(rhs.mClientInfo), + // mReservedClientSource must be handled specially during redirect + // mReservedClientInfo must be handled specially during redirect + // mInitialClientInfo must be handled specially during redirect + mController(rhs.mController), + mPerformanceStorage(rhs.mPerformanceStorage), + mLoadingContext(rhs.mLoadingContext), + mContextForTopLevelLoad(rhs.mContextForTopLevelLoad), + mSecurityFlags(rhs.mSecurityFlags), + mSandboxFlags(rhs.mSandboxFlags), + mTriggeringSandboxFlags(rhs.mTriggeringSandboxFlags), + mInternalContentPolicyType(rhs.mInternalContentPolicyType), + mTainting(rhs.mTainting), + mBlockAllMixedContent(rhs.mBlockAllMixedContent), + mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests), + mBrowserUpgradeInsecureRequests(rhs.mBrowserUpgradeInsecureRequests), + mBrowserDidUpgradeInsecureRequests( + rhs.mBrowserDidUpgradeInsecureRequests), + mBrowserWouldUpgradeInsecureRequests( + rhs.mBrowserWouldUpgradeInsecureRequests), + mForceAllowDataURI(rhs.mForceAllowDataURI), + mAllowInsecureRedirectToDataURI(rhs.mAllowInsecureRedirectToDataURI), + mSkipContentPolicyCheckForWebRequest( + rhs.mSkipContentPolicyCheckForWebRequest), + mOriginalFrameSrcLoad(rhs.mOriginalFrameSrcLoad), + mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped), + mInnerWindowID(rhs.mInnerWindowID), + mBrowsingContextID(rhs.mBrowsingContextID), + mWorkerAssociatedBrowsingContextID( + rhs.mWorkerAssociatedBrowsingContextID), + mFrameBrowsingContextID(rhs.mFrameBrowsingContextID), + mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone), + mIsThirdPartyContext(rhs.mIsThirdPartyContext), + mIsThirdPartyContextToTopWindow(rhs.mIsThirdPartyContextToTopWindow), + mIsFormSubmission(rhs.mIsFormSubmission), + mSendCSPViolationEvents(rhs.mSendCSPViolationEvents), + mOriginAttributes(rhs.mOriginAttributes), + mRedirectChainIncludingInternalRedirects( + rhs.mRedirectChainIncludingInternalRedirects.Clone()), + mRedirectChain(rhs.mRedirectChain.Clone()), + mAncestorPrincipals(rhs.mAncestorPrincipals.Clone()), + mAncestorBrowsingContextIDs(rhs.mAncestorBrowsingContextIDs.Clone()), + mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders.Clone()), + mRequestBlockingReason(rhs.mRequestBlockingReason), + mForcePreflight(rhs.mForcePreflight), + mIsPreflight(rhs.mIsPreflight), + mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal), + mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted), + mAllowListFutureDocumentsCreatedFromThisRedirectChain( + rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain), + mNeedForCheckingAntiTrackingHeuristic( + rhs.mNeedForCheckingAntiTrackingHeuristic), + mCspNonce(rhs.mCspNonce), + mSkipContentSniffing(rhs.mSkipContentSniffing), + mHttpsOnlyStatus(rhs.mHttpsOnlyStatus), + mHstsStatus(rhs.mHstsStatus), + mHasValidUserGestureActivation(rhs.mHasValidUserGestureActivation), + mAllowDeprecatedSystemRequests(rhs.mAllowDeprecatedSystemRequests), + mIsInDevToolsContext(rhs.mIsInDevToolsContext), + mParserCreatedScript(rhs.mParserCreatedScript), + mStoragePermission(rhs.mStoragePermission), + mIsMetaRefresh(rhs.mIsMetaRefresh), + mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes), + mIsMediaRequest(rhs.mIsMediaRequest), + mIsMediaInitialRequest(rhs.mIsMediaInitialRequest), + mIsFromObjectOrEmbed(rhs.mIsFromObjectOrEmbed), + mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + rhs.mIsOriginTrialCoepCredentiallessEnabledForTopLevel), + mUnstrippedURI(rhs.mUnstrippedURI), + mInterceptionInfo(rhs.mInterceptionInfo), + mHasInjectedCookieForCookieBannerHandling( + rhs.mHasInjectedCookieForCookieBannerHandling) {} + +LoadInfo::LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal, + nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings, + nsIContentSecurityPolicy* aCspToInherit, + const nsACString& aTriggeringRemoteType, + const nsID& aSandboxedNullPrincipalID, const Maybe& aClientInfo, + const Maybe& aReservedClientInfo, + const Maybe& aInitialClientInfo, + const Maybe& aController, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags, + uint32_t aTriggeringSandboxFlags, nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, bool aBlockAllMixedContent, + bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests, + bool aBrowserDidUpgradeInsecureRequests, + bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI, + bool aAllowInsecureRedirectToDataURI, + bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad, + bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID, + uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID, + bool aInitialSecurityCheckDone, bool aIsThirdPartyContext, + const Maybe& aIsThirdPartyContextToTopWindow, bool aIsFormSubmission, + bool aSendCSPViolationEvents, const OriginAttributes& aOriginAttributes, + RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects, + RedirectHistoryArray&& aRedirectChain, + nsTArray>&& aAncestorPrincipals, + const nsTArray& aAncestorBrowsingContextIDs, + const nsTArray& aCorsUnsafeHeaders, bool aForcePreflight, + bool aIsPreflight, bool aLoadTriggeredFromExternal, + bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted, + bool aAllowListFutureDocumentsCreatedFromThisRedirectChain, + bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce, + bool aSkipContentSniffing, uint32_t aHttpsOnlyStatus, bool aHstsStatus, + bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests, + bool aIsInDevToolsContext, bool aParserCreatedScript, + nsILoadInfo::StoragePermissionState aStoragePermission, bool aIsMetaRefresh, + uint32_t aRequestBlockingReason, nsINode* aLoadingContext, + nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, + nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo, + bool aHasInjectedCookieForCookieBannerHandling) + : mLoadingPrincipal(aLoadingPrincipal), + mTriggeringPrincipal(aTriggeringPrincipal), + mPrincipalToInherit(aPrincipalToInherit), + mTopLevelPrincipal(aTopLevelPrincipal), + mResultPrincipalURI(aResultPrincipalURI), + mCookieJarSettings(aCookieJarSettings), + mCspToInherit(aCspToInherit), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(aSandboxedNullPrincipalID), + mClientInfo(aClientInfo), + mReservedClientInfo(aReservedClientInfo), + mInitialClientInfo(aInitialClientInfo), + mController(aController), + mLoadingContext(do_GetWeakReference(aLoadingContext)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mTriggeringSandboxFlags(aTriggeringSandboxFlags), + mInternalContentPolicyType(aContentPolicyType), + mTainting(aTainting), + mBlockAllMixedContent(aBlockAllMixedContent), + mUpgradeInsecureRequests(aUpgradeInsecureRequests), + mBrowserUpgradeInsecureRequests(aBrowserUpgradeInsecureRequests), + mBrowserDidUpgradeInsecureRequests(aBrowserDidUpgradeInsecureRequests), + mBrowserWouldUpgradeInsecureRequests( + aBrowserWouldUpgradeInsecureRequests), + mForceAllowDataURI(aForceAllowDataURI), + mAllowInsecureRedirectToDataURI(aAllowInsecureRedirectToDataURI), + mSkipContentPolicyCheckForWebRequest( + aSkipContentPolicyCheckForWebRequest), + mOriginalFrameSrcLoad(aOriginalFrameSrcLoad), + mForceInheritPrincipalDropped(aForceInheritPrincipalDropped), + mInnerWindowID(aInnerWindowID), + mBrowsingContextID(aBrowsingContextID), + mFrameBrowsingContextID(aFrameBrowsingContextID), + mInitialSecurityCheckDone(aInitialSecurityCheckDone), + mIsThirdPartyContext(aIsThirdPartyContext), + mIsThirdPartyContextToTopWindow(aIsThirdPartyContextToTopWindow), + mIsFormSubmission(aIsFormSubmission), + mSendCSPViolationEvents(aSendCSPViolationEvents), + mOriginAttributes(aOriginAttributes), + mRedirectChainIncludingInternalRedirects( + std::move(aRedirectChainIncludingInternalRedirects)), + mRedirectChain(std::move(aRedirectChain)), + mAncestorPrincipals(std::move(aAncestorPrincipals)), + mAncestorBrowsingContextIDs(aAncestorBrowsingContextIDs.Clone()), + mCorsUnsafeHeaders(aCorsUnsafeHeaders.Clone()), + mRequestBlockingReason(aRequestBlockingReason), + mForcePreflight(aForcePreflight), + mIsPreflight(aIsPreflight), + mLoadTriggeredFromExternal(aLoadTriggeredFromExternal), + mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized), + mDocumentHasUserInteracted(aDocumentHasUserInteracted), + mAllowListFutureDocumentsCreatedFromThisRedirectChain( + aAllowListFutureDocumentsCreatedFromThisRedirectChain), + mNeedForCheckingAntiTrackingHeuristic( + aNeedForCheckingAntiTrackingHeuristic), + mCspNonce(aCspNonce), + mSkipContentSniffing(aSkipContentSniffing), + mHttpsOnlyStatus(aHttpsOnlyStatus), + mHstsStatus(aHstsStatus), + mHasValidUserGestureActivation(aHasValidUserGestureActivation), + mAllowDeprecatedSystemRequests(aAllowDeprecatedSystemRequests), + mIsInDevToolsContext(aIsInDevToolsContext), + mParserCreatedScript(aParserCreatedScript), + mStoragePermission(aStoragePermission), + mIsMetaRefresh(aIsMetaRefresh), + mLoadingEmbedderPolicy(aLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + aIsOriginTrialCoepCredentiallessEnabledForTopLevel), + mUnstrippedURI(aUnstrippedURI), + mInterceptionInfo(aInterceptionInfo), + mHasInjectedCookieForCookieBannerHandling( + aHasInjectedCookieForCookieBannerHandling) { + // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal + MOZ_ASSERT(mLoadingPrincipal || + aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT); + MOZ_ASSERT(mTriggeringPrincipal); +} + +// static +void LoadInfo::ComputeAncestors( + CanonicalBrowsingContext* aBC, + nsTArray>& aAncestorPrincipals, + nsTArray& aBrowsingContextIDs) { + MOZ_ASSERT(aAncestorPrincipals.IsEmpty()); + MOZ_ASSERT(aBrowsingContextIDs.IsEmpty()); + CanonicalBrowsingContext* ancestorBC = aBC; + // Iterate over ancestor WindowGlobalParents, collecting principals and outer + // window IDs. + while (WindowGlobalParent* ancestorWGP = + ancestorBC->GetParentWindowContext()) { + ancestorBC = ancestorWGP->BrowsingContext(); + + nsCOMPtr parentPrincipal = ancestorWGP->DocumentPrincipal(); + MOZ_ASSERT(parentPrincipal, "Ancestor principal is null"); + aAncestorPrincipals.AppendElement(parentPrincipal.forget()); + aBrowsingContextIDs.AppendElement(ancestorBC->Id()); + } +} +void LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) { + ExtContentPolicyType type = + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType); + if (type == ExtContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + nsCOMPtr util(do_GetService(THIRDPARTYUTIL_CONTRACTID)); + if (NS_WARN_IF(!util)) { + return; + } + + util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext); +} + +void LoadInfo::ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal) { + if (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + if (!thirdPartyUtil) { + return; + } + thirdPartyUtil->IsThirdPartyGlobal(aGlobal, &mIsThirdPartyContext); +} + +NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo) + +LoadInfo::~LoadInfo() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); } + +already_AddRefed LoadInfo::Clone() const { + RefPtr copy(new LoadInfo(*this)); + return copy.forget(); +} + +already_AddRefed LoadInfo::CloneWithNewSecFlags( + nsSecurityFlags aSecurityFlags) const { + RefPtr copy(new LoadInfo(*this)); + copy->mSecurityFlags = aSecurityFlags; + return copy.forget(); +} + +already_AddRefed LoadInfo::CloneForNewRequest() const { + RefPtr copy(new LoadInfo(*this)); + copy->mInitialSecurityCheckDone = false; + copy->mRedirectChainIncludingInternalRedirects.Clear(); + copy->mRedirectChain.Clear(); + copy->mResultPrincipalURI = nullptr; + return copy.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) { + *aLoadingPrincipal = do_AddRef(mLoadingPrincipal).take(); + return NS_OK; +} + +nsIPrincipal* LoadInfo::VirtualGetLoadingPrincipal() { + return mLoadingPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) { + *aTriggeringPrincipal = do_AddRef(mTriggeringPrincipal).take(); + return NS_OK; +} + +nsIPrincipal* LoadInfo::TriggeringPrincipal() { return mTriggeringPrincipal; } + +NS_IMETHODIMP +LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + *aPrincipalToInherit = do_AddRef(mPrincipalToInherit).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit"); + mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +nsIPrincipal* LoadInfo::PrincipalToInherit() { return mPrincipalToInherit; } + +nsIPrincipal* LoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) { + if (mPrincipalToInherit) { + return mPrincipalToInherit; + } + + nsCOMPtr uri = mResultPrincipalURI; + if (!uri) { + Unused << aChannel->GetOriginalURI(getter_AddRefs(uri)); + } + + auto* prin = BasePrincipal::Cast(mTriggeringPrincipal); + return prin->PrincipalToInherit(uri); +} + +const nsID& LoadInfo::GetSandboxedNullPrincipalID() { + MOZ_ASSERT(!mSandboxedNullPrincipalID.Equals(nsID{}), + "mSandboxedNullPrincipalID wasn't initialized?"); + return mSandboxedNullPrincipalID; +} + +void LoadInfo::ResetSandboxedNullPrincipalID() { + mSandboxedNullPrincipalID = nsID::GenerateUUID(); +} + +nsIPrincipal* LoadInfo::GetTopLevelPrincipal() { return mTopLevelPrincipal; } + +NS_IMETHODIMP +LoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) { + aTriggeringRemoteType = mTriggeringRemoteType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) { + mTriggeringRemoteType = aTriggeringRemoteType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingDocument(Document** aResult) { + if (nsCOMPtr node = do_QueryReferent(mLoadingContext)) { + RefPtr context = node->OwnerDoc(); + context.forget(aResult); + } + return NS_OK; +} + +nsINode* LoadInfo::LoadingNode() { + nsCOMPtr node = do_QueryReferent(mLoadingContext); + return node; +} + +already_AddRefed LoadInfo::ContextForTopLevelLoad() { + // Most likely you want to query LoadingNode() instead of + // ContextForTopLevelLoad() if this assertion fires. + MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "should only query this context for top level document loads"); + nsCOMPtr context = do_QueryReferent(mContextForTopLevelLoad); + return context.forget(); +} + +already_AddRefed LoadInfo::GetLoadingContext() { + nsCOMPtr context; + if (mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + context = ContextForTopLevelLoad(); + } else { + context = LoadingNode(); + } + return context.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) { + nsCOMPtr context = GetLoadingContext(); + context.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) { + *aResult = mSecurityFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSandboxFlags(uint32_t* aResult) { + *aResult = mSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) { + *aResult = mTriggeringSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringSandboxFlags(uint32_t aFlags) { + mTriggeringSandboxFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityMode(uint32_t* aFlags) { + *aFlags = (mSecurityFlags & + (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT | + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL | + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) { + *aIsInThirdPartyContext = mIsThirdPartyContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) { + mIsThirdPartyContext = aIsInThirdPartyContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsThirdPartyContextToTopWindow( + bool* aIsThirdPartyContextToTopWindow) { + *aIsThirdPartyContextToTopWindow = + mIsThirdPartyContextToTopWindow.valueOr(true); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsThirdPartyContextToTopWindow( + bool aIsThirdPartyContextToTopWindow) { + mIsThirdPartyContextToTopWindow = Some(aIsThirdPartyContextToTopWindow); + return NS_OK; +} + +static const uint32_t sCookiePolicyMask = + nsILoadInfo::SEC_COOKIES_DEFAULT | nsILoadInfo::SEC_COOKIES_INCLUDE | + nsILoadInfo::SEC_COOKIES_SAME_ORIGIN | nsILoadInfo::SEC_COOKIES_OMIT; + +NS_IMETHODIMP +LoadInfo::GetCookiePolicy(uint32_t* aResult) { + uint32_t policy = mSecurityFlags & sCookiePolicyMask; + if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) { + policy = (mSecurityFlags & SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) + ? nsILoadInfo::SEC_COOKIES_SAME_ORIGIN + : nsILoadInfo::SEC_COOKIES_INCLUDE; + } + + *aResult = policy; + return NS_OK; +} + +namespace { + +already_AddRefed CreateCookieJarSettings( + nsContentPolicyType aContentPolicyType, bool aIsPrivate, + bool shouldResistFingerprinting) { + if (StaticPrefs::network_cookieJarSettings_unblocked_for_testing()) { + return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting) + : CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + + // These contentPolictTypes require a real CookieJarSettings because favicon + // and save-as requests must send cookies. Anything else should not + // send/receive cookies. + if (aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON || + aContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD) { + return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting) + : CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + + return CookieJarSettings::GetBlockingAll(shouldResistFingerprinting); +} + +} // namespace + +NS_IMETHODIMP +LoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) { + if (!mCookieJarSettings) { + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + nsCOMPtr loadingPrincipal; + Unused << this->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)); + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + loadingPrincipal, + "CookieJarSettings can't exist yet, we're creating it"); + mCookieJarSettings = CreateCookieJarSettings( + mInternalContentPolicyType, isPrivate, shouldResistFingerprinting); + } + + nsCOMPtr cookieJarSettings = mCookieJarSettings; + cookieJarSettings.forget(aCookieJarSettings); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) { + MOZ_ASSERT(aCookieJarSettings); + // We allow the overwrite of CookieJarSettings. + mCookieJarSettings = aCookieJarSettings; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetStoragePermission( + nsILoadInfo::StoragePermissionState* aStoragePermission) { + *aStoragePermission = mStoragePermission; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetStoragePermission( + nsILoadInfo::StoragePermissionState aStoragePermission) { + mStoragePermission = aStoragePermission; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsMetaRefresh(bool* aIsMetaRefresh) { + *aIsMetaRefresh = mIsMetaRefresh; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMetaRefresh(bool aIsMetaRefresh) { + mIsMetaRefresh = aIsMetaRefresh; + return NS_OK; +} + +void LoadInfo::SetIncludeCookiesSecFlag() { + MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) == + nsILoadInfo::SEC_COOKIES_DEFAULT); + mSecurityFlags = + (mSecurityFlags & ~sCookiePolicyMask) | nsILoadInfo::SEC_COOKIES_INCLUDE; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) { + *aInheritPrincipal = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) { + *aInheritPrincipal = + (mSecurityFlags & + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) { + *aLoadingSandboxed = (mSandboxFlags & SANDBOXED_ORIGIN); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAboutBlankInherits(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowChrome(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetDisallowScript(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetDontFollowRedirects(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadErrorPage(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsFormSubmission(bool* aResult) { + *aResult = mIsFormSubmission; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsFormSubmission(bool aValue) { + mIsFormSubmission = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSendCSPViolationEvents(bool* aResult) { + *aResult = mSendCSPViolationEvents; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSendCSPViolationEvents(bool aValue) { + mSendCSPViolationEvents = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) { + // We have to use nsContentPolicyType because ExtContentPolicyType is not + // visible from xpidl. + *aResult = static_cast( + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType)); + return NS_OK; +} + +nsContentPolicyType LoadInfo::InternalContentPolicyType() { + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +LoadInfo::GetBlockAllMixedContent(bool* aResult) { + *aResult = mBlockAllMixedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetUpgradeInsecureRequests(bool* aResult) { + *aResult = mUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserDidUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserWouldUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) { + MOZ_ASSERT(!mForceAllowDataURI || + mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only allow data URI navigation for TYPE_DOCUMENT"); + mForceAllowDataURI = aForceAllowDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) { + *aForceAllowDataURI = mForceAllowDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowInsecureRedirectToDataURI( + bool aAllowInsecureRedirectToDataURI) { + mAllowInsecureRedirectToDataURI = aAllowInsecureRedirectToDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowInsecureRedirectToDataURI( + bool* aAllowInsecureRedirectToDataURI) { + *aAllowInsecureRedirectToDataURI = mAllowInsecureRedirectToDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) { + mSkipContentPolicyCheckForWebRequest = aSkip; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) { + *aSkip = mSkipContentPolicyCheckForWebRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) { + mOriginalFrameSrcLoad = aOriginalFrameSrcLoad; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) { + *aOriginalFrameSrcLoad = mOriginalFrameSrcLoad; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalDropped(bool* aResult) { + *aResult = mForceInheritPrincipalDropped; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInnerWindowID(uint64_t* aResult) { + *aResult = mInnerWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowsingContextID(uint64_t* aResult) { + *aResult = mBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) { + *aResult = mWorkerAssociatedBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aID) { + mWorkerAssociatedBrowsingContextID = aID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) { + *aResult = mFrameBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) { + return (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_SUBDOCUMENT) + ? GetFrameBrowsingContextID(aResult) + : GetBrowsingContextID(aResult); +} + +NS_IMETHODIMP +LoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetWorkerAssociatedBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mWorkerAssociatedBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mFrameBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) { + uint64_t targetBrowsingContextID = 0; + MOZ_ALWAYS_SUCCEEDS(GetTargetBrowsingContextID(&targetBrowsingContextID)); + *aResult = BrowsingContext::Get(targetBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle aOriginAttributes) { + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::ResetPrincipalToInheritToNullPrincipal() { + // take the originAttributes from the LoadInfo and create + // a new NullPrincipal using those origin attributes. + nsCOMPtr newNullPrincipal = + NullPrincipal::Create(mOriginAttributes); + + mPrincipalToInherit = newNullPrincipal; + + // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule + // any non null owner set on the channel and will return the principal + // form the loadinfo instead. + mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER; + + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetScriptableOriginAttributes( + JSContext* aCx, JS::Handle aOriginAttributes) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult LoadInfo::GetOriginAttributes( + mozilla::OriginAttributes* aOriginAttributes) { + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult LoadInfo::SetOriginAttributes( + const mozilla::OriginAttributes& aOriginAttributes) { + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) { + // Indicates whether the channel was ever evaluated by the + // ContentSecurityManager. Once set to true, this flag must + // remain true throughout the lifetime of the channel. + // Setting it to anything else than true will be discarded. + MOZ_ASSERT(aInitialSecurityCheckDone, + "aInitialSecurityCheckDone must be true"); + mInitialSecurityCheckDone = + mInitialSecurityCheckDone || aInitialSecurityCheckDone; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInitialSecurityCheckDone(bool* aResult) { + *aResult = mInitialSecurityCheckDone; + return NS_OK; +} + +// To prevent unintenional credential and information leaks in content +// processes we can use this function to truncate a Principal's URI as much as +// possible. +already_AddRefed CreateTruncatedPrincipal( + nsIPrincipal* aPrincipal) { + nsCOMPtr truncatedPrincipal; + // System Principal URIs don't need to be truncated as they don't contain any + // sensitive browsing history information. + if (aPrincipal->IsSystemPrincipal()) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Content Principal URIs are the main location of the information we need to + // truncate. + if (aPrincipal->GetIsContentPrincipal()) { + // Certain URIs (chrome, resource, about) don't need to be truncated as they + // should be free of any sensitive user browsing history. + if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") || + aPrincipal->SchemeIs("about")) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Different parts of the URI are preserved due to being vital to the + // browser's operation. + // Scheme for differentiating between different types of URIs and how to + // truncate them and later on utilize them. + // Host and Port to retain the redirect chain's core functionality. + // Path would ideally be removed but needs to be retained to ensure that + // http/https redirect loops can be detected. + // The entirety of the Query String, Reference Fragment, and User Info + // subcomponents must be stripped to avoid leaking Oauth tokens, user + // identifiers, and similar bits of information that these subcomponents may + // contain. + nsAutoCString scheme; + nsAutoCString separator("://"); + nsAutoCString hostPort; + nsAutoCString path; + nsAutoCString uriString(""); + if (aPrincipal->SchemeIs("view-source")) { + // The path portion of the view-source URI will be the URI whose source is + // being viewed, so we create a new URI object with a truncated form of + // the path and append the view-source scheme to the front again. + nsAutoCString viewSourcePath; + aPrincipal->GetFilePath(viewSourcePath); + + nsCOMPtr nestedURI; + nsresult rv = NS_NewURI(getter_AddRefs(nestedURI), viewSourcePath); + + if (NS_FAILED(rv)) { + // Since the path here should be an already validated URI this should + // never happen. + NS_WARNING(viewSourcePath.get()); + MOZ_ASSERT(false, + "Failed to create truncated form of URI with NS_NewURI."); + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + nestedURI->GetScheme(scheme); + nestedURI->GetHostPort(hostPort); + nestedURI->GetFilePath(path); + uriString += "view-source:"; + } else { + aPrincipal->GetScheme(scheme); + aPrincipal->GetHostPort(hostPort); + aPrincipal->GetFilePath(path); + } + uriString += scheme + separator + hostPort + path; + + nsCOMPtr truncatedURI; + nsresult rv = NS_NewURI(getter_AddRefs(truncatedURI), uriString); + if (NS_FAILED(rv)) { + NS_WARNING(uriString.get()); + MOZ_ASSERT(false, + "Failed to create truncated form of URI with NS_NewURI."); + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + return BasePrincipal::CreateContentPrincipal( + truncatedURI, aPrincipal->OriginAttributesRef()); + } + + // Null Principal Precursor URIs can also contain information that needs to + // be truncated. + if (aPrincipal->GetIsNullPrincipal()) { + nsCOMPtr precursorPrincipal = + aPrincipal->GetPrecursorPrincipal(); + // If there is no precursor then nothing needs to be truncated. + if (!precursorPrincipal) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Otherwise we return a new Null Principal with the original's Origin + // Attributes and a truncated version of the original's precursor URI. + nsCOMPtr truncatedPrecursor = + CreateTruncatedPrincipal(precursorPrincipal); + return NullPrincipal::CreateWithInheritedAttributes(truncatedPrecursor); + } + + // Expanded Principals shouldn't contain sensitive information but their + // allowlists might so we truncate that information here. + if (aPrincipal->GetIsExpandedPrincipal()) { + nsTArray> truncatedAllowList; + + for (const auto& allowedPrincipal : BasePrincipal::Cast(aPrincipal) + ->As() + ->AllowList()) { + nsCOMPtr truncatedPrincipal = + CreateTruncatedPrincipal(allowedPrincipal); + + truncatedAllowList.AppendElement(truncatedPrincipal); + } + + return ExpandedPrincipal::Create(truncatedAllowList, + aPrincipal->OriginAttributesRef()); + } + + // If we hit this assertion we need to update this function to add the + // Principals and URIs seen as new corner cases to handle. + MOZ_ASSERT(false, "Unhandled Principal or URI type encountered."); + + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); +} + +NS_IMETHODIMP +LoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannel, + bool aIsInternalRedirect) { + NS_ENSURE_ARG(aChannel); + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr uriPrincipal; + nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager(); + sm->GetChannelURIPrincipal(aChannel, getter_AddRefs(uriPrincipal)); + + nsCOMPtr referrer; + nsCString remoteAddress; + + nsCOMPtr httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + nsCOMPtr referrerInfo; + Unused << httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)); + if (referrerInfo) { + referrer = referrerInfo->GetComputedReferrer(); + } + + nsCOMPtr intChannel(do_QueryInterface(aChannel)); + if (intChannel) { + Unused << intChannel->GetRemoteAddress(remoteAddress); + } + } + + nsCOMPtr truncatedPrincipal = + CreateTruncatedPrincipal(uriPrincipal); + + nsCOMPtr entry = + new nsRedirectHistoryEntry(truncatedPrincipal, referrer, remoteAddress); + + mRedirectChainIncludingInternalRedirects.AppendElement(entry); + if (!aIsInternalRedirect) { + mRedirectChain.AppendElement(entry); + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirects(JSContext* aCx, JS::MutableHandle aRedirects, + const RedirectHistoryArray& aArray) { + JS::Rooted redirects(aCx, + JS::NewArrayObject(aCx, aArray.Length())); + NS_ENSURE_TRUE(redirects, NS_ERROR_OUT_OF_MEMORY); + + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); + + nsCOMPtr xpc = nsIXPConnect::XPConnect(); + + for (size_t idx = 0; idx < aArray.Length(); idx++) { + JS::Rooted jsobj(aCx); + nsresult rv = + xpc->WrapNative(aCx, global, aArray[idx], + NS_GET_IID(nsIRedirectHistoryEntry), jsobj.address()); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_STATE(jsobj); + + bool rc = JS_DefineElement(aCx, redirects, idx, jsobj, JSPROP_ENUMERATE); + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); + } + + aRedirects.setObject(*redirects); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChainIncludingInternalRedirects( + JSContext* aCx, JS::MutableHandle aChain) { + return GetRedirects(aCx, aChain, mRedirectChainIncludingInternalRedirects); +} + +const RedirectHistoryArray& +LoadInfo::RedirectChainIncludingInternalRedirects() { + return mRedirectChainIncludingInternalRedirects; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChain(JSContext* aCx, + JS::MutableHandle aChain) { + return GetRedirects(aCx, aChain, mRedirectChain); +} + +const RedirectHistoryArray& LoadInfo::RedirectChain() { return mRedirectChain; } + +const nsTArray>& LoadInfo::AncestorPrincipals() { + return mAncestorPrincipals; +} + +const nsTArray& LoadInfo::AncestorBrowsingContextIDs() { + return mAncestorBrowsingContextIDs; +} + +void LoadInfo::SetCorsPreflightInfo(const nsTArray& aHeaders, + bool aForcePreflight) { + MOZ_ASSERT(GetSecurityMode() == + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mCorsUnsafeHeaders = aHeaders.Clone(); + mForcePreflight = aForcePreflight; +} + +const nsTArray& LoadInfo::CorsUnsafeHeaders() { + return mCorsUnsafeHeaders; +} + +NS_IMETHODIMP +LoadInfo::GetForcePreflight(bool* aForcePreflight) { + *aForcePreflight = mForcePreflight; + return NS_OK; +} + +void LoadInfo::SetIsPreflight() { + MOZ_ASSERT(GetSecurityMode() == + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mIsPreflight = true; +} + +void LoadInfo::SetUpgradeInsecureRequests(bool aValue) { + mUpgradeInsecureRequests = aValue; +} + +void LoadInfo::SetBrowserUpgradeInsecureRequests() { + mBrowserUpgradeInsecureRequests = true; +} + +NS_IMETHODIMP +LoadInfo::SetBrowserDidUpgradeInsecureRequests( + bool aBrowserDidUpgradeInsecureRequests) { + mBrowserDidUpgradeInsecureRequests = aBrowserDidUpgradeInsecureRequests; + return NS_OK; +} + +void LoadInfo::SetBrowserWouldUpgradeInsecureRequests() { + mBrowserWouldUpgradeInsecureRequests = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsPreflight(bool* aIsPreflight) { + *aIsPreflight = mIsPreflight; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) { + MOZ_ASSERT(!aLoadTriggeredFromExternal || + mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only set load triggered from external for TYPE_DOCUMENT"); + mLoadTriggeredFromExternal = aLoadTriggeredFromExternal; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) { + *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetServiceWorkerTaintingSynthesized( + bool* aServiceWorkerTaintingSynthesized) { + MOZ_ASSERT(aServiceWorkerTaintingSynthesized); + *aServiceWorkerTaintingSynthesized = mServiceWorkerTaintingSynthesized; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTainting(uint32_t* aTaintingOut) { + MOZ_ASSERT(aTaintingOut); + *aTaintingOut = static_cast(mTainting); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::MaybeIncreaseTainting(uint32_t aTainting) { + NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE); + + // Skip if the tainting has been set by the service worker. + if (mServiceWorkerTaintingSynthesized) { + return NS_OK; + } + + LoadTainting tainting = static_cast(aTainting); + if (tainting > mTainting) { + mTainting = tainting; + } + return NS_OK; +} + +void LoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) { + MOZ_DIAGNOSTIC_ASSERT(aTainting <= LoadTainting::Opaque); + mTainting = aTainting; + + // Flag to prevent the tainting from being increased. + mServiceWorkerTaintingSynthesized = true; +} + +NS_IMETHODIMP +LoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) { + MOZ_ASSERT(aDocumentHasUserInteracted); + *aDocumentHasUserInteracted = mDocumentHasUserInteracted; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) { + mDocumentHasUserInteracted = aDocumentHasUserInteracted; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool* aValue) { + MOZ_ASSERT(aValue); + *aValue = mAllowListFutureDocumentsCreatedFromThisRedirectChain; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(bool aValue) { + mAllowListFutureDocumentsCreatedFromThisRedirectChain = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) { + MOZ_ASSERT(aValue); + *aValue = mNeedForCheckingAntiTrackingHeuristic; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) { + mNeedForCheckingAntiTrackingHeuristic = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetCspNonce(nsAString& aCspNonce) { + aCspNonce = mCspNonce; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCspNonce(const nsAString& aCspNonce) { + MOZ_ASSERT(!mInitialSecurityCheckDone, + "setting the nonce is only allowed before any sec checks"); + mCspNonce = aCspNonce; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) { + *aSkipContentSniffing = mSkipContentSniffing; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) { + mSkipContentSniffing = aSkipContentSniffing; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) { + *aHttpsOnlyStatus = mHttpsOnlyStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) { + mHttpsOnlyStatus = aHttpsOnlyStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHstsStatus(bool* aHstsStatus) { + *aHstsStatus = mHstsStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHstsStatus(bool aHstsStatus) { + mHstsStatus = aHstsStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHasValidUserGestureActivation( + bool* aHasValidUserGestureActivation) { + *aHasValidUserGestureActivation = mHasValidUserGestureActivation; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHasValidUserGestureActivation( + bool aHasValidUserGestureActivation) { + mHasValidUserGestureActivation = aHasValidUserGestureActivation; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowDeprecatedSystemRequests( + bool* aAllowDeprecatedSystemRequests) { + *aAllowDeprecatedSystemRequests = mAllowDeprecatedSystemRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowDeprecatedSystemRequests( + bool aAllowDeprecatedSystemRequests) { + mAllowDeprecatedSystemRequests = aAllowDeprecatedSystemRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) { + *aIsUserTriggeredSave = + mIsUserTriggeredSave || + mInternalContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) { + mIsUserTriggeredSave = aIsUserTriggeredSave; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) { + *aIsInDevToolsContext = mIsInDevToolsContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) { + mIsInDevToolsContext = aIsInDevToolsContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) { + *aParserCreatedScript = mParserCreatedScript; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetParserCreatedScript(bool aParserCreatedScript) { + mParserCreatedScript = aParserCreatedScript; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsTopLevelLoad(bool* aResult) { + RefPtr bc; + GetTargetBrowsingContext(getter_AddRefs(bc)); + *aResult = !bc || bc->IsTop(); + return NS_OK; +} + +void LoadInfo::SetIsFromProcessingFrameAttributes() { + mIsFromProcessingFrameAttributes = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsFromProcessingFrameAttributes( + bool* aIsFromProcessingFrameAttributes) { + MOZ_ASSERT(aIsFromProcessingFrameAttributes); + *aIsFromProcessingFrameAttributes = mIsFromProcessingFrameAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMediaRequest(bool aIsMediaRequest) { + mIsMediaRequest = aIsMediaRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) { + MOZ_ASSERT(aIsMediaRequest); + *aIsMediaRequest = mIsMediaRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) { + mIsMediaInitialRequest = aIsMediaInitialRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) { + MOZ_ASSERT(aIsMediaInitialRequest); + *aIsMediaInitialRequest = mIsMediaInitialRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) { + mIsFromObjectOrEmbed = aIsFromObjectOrEmbed; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) { + MOZ_ASSERT(aIsFromObjectOrEmbed); + *aIsFromObjectOrEmbed = mIsFromObjectOrEmbed; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized( + bool* aShouldSkipCheckForBrokenURLOrZeroSized) { + MOZ_ASSERT(aShouldSkipCheckForBrokenURLOrZeroSized); + *aShouldSkipCheckForBrokenURLOrZeroSized = mSkipCheckForBrokenURLOrZeroSized; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetResultPrincipalURI(nsIURI** aURI) { + *aURI = do_AddRef(mResultPrincipalURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetResultPrincipalURI(nsIURI* aURI) { + mResultPrincipalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) { + *aURI = do_AddRef(mChannelCreationOriginalURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) { + mChannelCreationOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetRequestBlockingReason(uint32_t aReason) { + mRequestBlockingReason = aReason; + return NS_OK; +} +NS_IMETHODIMP +LoadInfo::GetRequestBlockingReason(uint32_t* aReason) { + *aReason = mRequestBlockingReason; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetUnstrippedURI(nsIURI** aURI) { + *aURI = do_AddRef(mUnstrippedURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetUnstrippedURI(nsIURI* aURI) { + mUnstrippedURI = aURI; + return NS_OK; +} + +void LoadInfo::SetClientInfo(const ClientInfo& aClientInfo) { + mClientInfo.emplace(aClientInfo); +} + +const Maybe& LoadInfo::GetClientInfo() { return mClientInfo; } + +void LoadInfo::GiveReservedClientSource( + UniquePtr&& aClientSource) { + MOZ_DIAGNOSTIC_ASSERT(aClientSource); + mReservedClientSource = std::move(aClientSource); + SetReservedClientInfo(mReservedClientSource->Info()); +} + +UniquePtr LoadInfo::TakeReservedClientSource() { + if (mReservedClientSource) { + // If the reserved ClientInfo was set due to a ClientSource being present, + // then clear that info object when the ClientSource is taken. + mReservedClientInfo.reset(); + } + return std::move(mReservedClientSource); +} + +void LoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) { + MOZ_DIAGNOSTIC_ASSERT(mInitialClientInfo.isNothing()); + // Treat assignments of the same value as a no-op. The emplace below + // will normally assert when overwriting an existing value. + if (mReservedClientInfo.isSome()) { + if (mReservedClientInfo.ref() == aClientInfo) { + return; + } + MOZ_DIAGNOSTIC_ASSERT(false, "mReservedClientInfo already set"); + mReservedClientInfo.reset(); + } + mReservedClientInfo.emplace(aClientInfo); +} + +void LoadInfo::OverrideReservedClientInfoInParent( + const ClientInfo& aClientInfo) { + // This should only be called to handle redirects in the parent process. + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + mInitialClientInfo.reset(); + mReservedClientInfo.reset(); + mReservedClientInfo.emplace(aClientInfo); +} + +const Maybe& LoadInfo::GetReservedClientInfo() { + return mReservedClientInfo; +} + +void LoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) { + MOZ_DIAGNOSTIC_ASSERT(!mReservedClientSource); + MOZ_DIAGNOSTIC_ASSERT(mReservedClientInfo.isNothing()); + // Treat assignments of the same value as a no-op. The emplace below + // will normally assert when overwriting an existing value. + if (mInitialClientInfo.isSome() && mInitialClientInfo.ref() == aClientInfo) { + return; + } + mInitialClientInfo.emplace(aClientInfo); +} + +const Maybe& LoadInfo::GetInitialClientInfo() { + return mInitialClientInfo; +} + +void LoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) { + mController.emplace(aServiceWorker); +} + +void LoadInfo::ClearController() { mController.reset(); } + +const Maybe& LoadInfo::GetController() { + return mController; +} + +void LoadInfo::SetPerformanceStorage(PerformanceStorage* aPerformanceStorage) { + mPerformanceStorage = aPerformanceStorage; +} + +PerformanceStorage* LoadInfo::GetPerformanceStorage() { + if (mPerformanceStorage) { + return mPerformanceStorage; + } + + auto* innerWindow = nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID); + if (!innerWindow) { + return nullptr; + } + + if (!TriggeringPrincipal()->Equals(innerWindow->GetPrincipal())) { + return nullptr; + } + + if (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_SUBDOCUMENT && + !GetIsFromProcessingFrameAttributes()) { + // We only report loads caused by processing the attributes of the + // browsing context container. + return nullptr; + } + + mozilla::dom::Performance* performance = innerWindow->GetPerformance(); + if (!performance) { + return nullptr; + } + + return performance->AsPerformanceStorage(); +} + +NS_IMETHODIMP +LoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) { + *aCSPEventListener = do_AddRef(mCSPEventListener).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) { + mCSPEventListener = aCSPEventListener; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) { + *aResult = mInternalContentPolicyType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { + *aOutPolicy = mLoadingEmbedderPolicy; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) { + mLoadingEmbedderPolicy = aPolicy; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + *aIsOriginTrialCoepCredentiallessEnabledForTopLevel = + mIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + aIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + +already_AddRefed LoadInfo::GetCsp() { + // Before querying the CSP from the client we have to check if the + // triggeringPrincipal originates from an addon and potentially + // overrides the CSP stored within the client. + if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mLoadingPrincipal)) { + nsCOMPtr ep = do_QueryInterface(mTriggeringPrincipal); + nsCOMPtr addonCSP; + if (ep) { + addonCSP = ep->GetCsp(); + } + return addonCSP.forget(); + } + + if (mClientInfo.isNothing()) { + return nullptr; + } + + nsCOMPtr node = do_QueryReferent(mLoadingContext); + RefPtr doc = node ? node->OwnerDoc() : nullptr; + + // If the client is of type window, then we return the cached CSP + // stored on the document instead of having to deserialize the CSP + // from the ClientInfo. + if (doc && mClientInfo->Type() == ClientType::Window) { + nsCOMPtr docCSP = doc->GetCsp(); + return docCSP.forget(); + } + + Maybe cspInfo = mClientInfo->GetCspInfo(); + if (cspInfo.isNothing()) { + return nullptr; + } + nsCOMPtr clientCSP = + CSPInfoToCSP(cspInfo.ref(), doc); + return clientCSP.forget(); +} + +already_AddRefed LoadInfo::GetPreloadCsp() { + if (mClientInfo.isNothing()) { + return nullptr; + } + + nsCOMPtr node = do_QueryReferent(mLoadingContext); + RefPtr doc = node ? node->OwnerDoc() : nullptr; + + // If the client is of type window, then we return the cached CSP + // stored on the document instead of having to deserialize the CSP + // from the ClientInfo. + if (doc && mClientInfo->Type() == ClientType::Window) { + nsCOMPtr preloadCsp = doc->GetPreloadCsp(); + return preloadCsp.forget(); + } + + Maybe cspInfo = mClientInfo->GetPreloadCspInfo(); + if (cspInfo.isNothing()) { + return nullptr; + } + nsCOMPtr preloadCSP = + CSPInfoToCSP(cspInfo.ref(), doc); + return preloadCSP.forget(); +} + +already_AddRefed LoadInfo::GetCspToInherit() { + nsCOMPtr cspToInherit = mCspToInherit; + return cspToInherit.forget(); +} + +nsIInterceptionInfo* LoadInfo::InterceptionInfo() { return mInterceptionInfo; } + +void LoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aInfo) { + mInterceptionInfo = aInfo; +} + +NS_IMETHODIMP +LoadInfo::GetHasInjectedCookieForCookieBannerHandling( + bool* aHasInjectedCookieForCookieBannerHandling) { + *aHasInjectedCookieForCookieBannerHandling = + mHasInjectedCookieForCookieBannerHandling; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHasInjectedCookieForCookieBannerHandling( + bool aHasInjectedCookieForCookieBannerHandling) { + mHasInjectedCookieForCookieBannerHandling = + aHasInjectedCookieForCookieBannerHandling; + return NS_OK; +} + +} // namespace mozilla::net diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h new file mode 100644 index 0000000000..8e9424a2d2 --- /dev/null +++ b/netwerk/base/LoadInfo.h @@ -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/. */ + +#ifndef mozilla_LoadInfo_h +#define mozilla_LoadInfo_h + +#include "nsIContentSecurityPolicy.h" +#include "nsIInterceptionInfo.h" +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nsIURI.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" + +class nsDocShell; +class nsICookieJarSettings; +class nsINode; +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace dom { +class PerformanceStorage; +class XMLHttpRequestMainThread; +class CanonicalBrowsingContext; +class WindowGlobalParent; +} // namespace dom + +namespace net { +class LoadInfoArgs; +class LoadInfo; +} // namespace net + +namespace ipc { +// we have to forward declare that function so we can use it as a friend. +nsresult LoadInfoArgsToLoadInfo( + const Maybe& aLoadInfoArgs, + const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext, + net::LoadInfo** outLoadInfo); +} // namespace ipc + +namespace net { + +using RedirectHistoryArray = nsTArray>; + +/** + * Class that provides an nsILoadInfo implementation. + */ +class LoadInfo final : public nsILoadInfo { + template + friend already_AddRefed mozilla::MakeAndAddRef(Args&&... aArgs); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADINFO + + // Used for TYPE_DOCUMENT load. + static already_AddRefed CreateForDocument( + dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // Used for TYPE_FRAME or TYPE_IFRAME load. + static already_AddRefed CreateForFrame( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // Use for non-{TYPE_DOCUMENT|TYPE_FRAME|TYPE_IFRAME} load. + static already_AddRefed CreateForNonDocument( + dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // aLoadingPrincipal MUST NOT BE NULL. + LoadInfo(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const Maybe& aLoadingClientInfo = + Maybe(), + const Maybe& aController = + Maybe(), + uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = 0); + + // Constructor used for TYPE_DOCUMENT loads which have a different + // loadingContext than other loads. This ContextForTopLevelLoad is + // only used for content policy checks. + LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + nsISupports* aContextForTopLevelLoad, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + private: + // Use factory function CreateForDocument + // Used for TYPE_DOCUMENT load. + LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + // Use factory function CreateForFrame + // Used for TYPE_FRAME or TYPE_IFRAME load. + LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + // Used for loads initiated by DocumentLoadListener that are not TYPE_DOCUMENT + // | TYPE_FRAME | TYPE_FRAME. + LoadInfo(dom::WindowGlobalParent* aParentWGP, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsContentPolicyType aContentPolicyType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + public: + // Compute a list of ancestor principals and BrowsingContext IDs. + // See methods AncestorPrincipals and AncestorBrowsingContextIDs + // in nsILoadInfo.idl for details. + static void ComputeAncestors( + dom::CanonicalBrowsingContext* aBC, + nsTArray>& aAncestorPrincipals, + nsTArray& aBrowsingContextIDs); + + // create an exact copy of the loadinfo + already_AddRefed 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 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 CloneForNewRequest() const; + + // The `nsContentPolicyType GetExternalContentPolicyType()` version in the + // base class is hidden by the implementation of + // `GetExternalContentPolicyType(nsContentPolicyType* aResult)` in + // LoadInfo.cpp. Explicit mark it visible. + using nsILoadInfo::GetExternalContentPolicyType; + + void SetIsPreflight(); + void SetUpgradeInsecureRequests(bool aValue); + void SetBrowserUpgradeInsecureRequests(); + void SetBrowserWouldUpgradeInsecureRequests(); + void SetIsFromProcessingFrameAttributes(); + + // Hands off from the cspToInherit functionality! + // + // For navigations, GetCSPToInherit returns what the spec calls the + // "request's client's global object's CSP list", or more precisely + // a snapshot of it taken when the navigation starts. For navigations + // that need to inherit their CSP, this is the right CSP to use for + // the new document. We need a way to transfer the CSP from the + // docshell (where the navigation starts) to the point where the new + // document is created and decides whether to inherit its CSP, and + // this is the mechanism we use for that. + // + // For example: + // A document with a CSP triggers a new top-level data: URI load. + // We pass the CSP of the document that triggered the load all the + // way to docshell. Within docshell we call SetCSPToInherit() on the + // loadinfo. Within Document::InitCSP() we check if the newly created + // document needs to inherit the CSP. If so, we call GetCSPToInherit() + // and set the inherited CSP as the CSP for the new document. Please + // note that any additonal Meta CSP in that document will be merged + // into that CSP. Any subresource loads within that document + // subesquently will receive the correct CSP by querying + // loadinfo->GetCsp() from that point on. + void SetCSPToInherit(nsIContentSecurityPolicy* aCspToInherit) { + mCspToInherit = aCspToInherit; + } + + bool HasIsThirdPartyContextToTopWindowSet() { + return mIsThirdPartyContextToTopWindow.isSome(); + } + void ClearIsThirdPartyContextToTopWindow() { + mIsThirdPartyContextToTopWindow.reset(); + } + + private: + // private constructor that is only allowed to be called from within + // HttpChannelParent and FTPChannelParent declared as friends undeneath. + // In e10s we can not serialize nsINode, hence we store the innerWindowID. + // Please note that aRedirectChain uses swapElements. + LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal, + nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings, + nsIContentSecurityPolicy* aCspToInherit, + const nsACString& aTriggeringRemoteType, + const nsID& aSandboxedNullPrincipalID, + const Maybe& aClientInfo, + const Maybe& aReservedClientInfo, + const Maybe& aInitialClientInfo, + const Maybe& aController, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags, + uint32_t aTriggeringSandboxFlags, nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, bool aBlockAllMixedContent, + bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests, + bool aBrowserDidUpgradeInsecureRequests, + bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI, + bool aAllowInsecureRedirectToDataURI, + bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad, + bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID, + uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID, + bool aInitialSecurityCheckDone, bool aIsThirdPartyContext, + const Maybe& aIsThirdPartyContextToTopWindow, + bool aIsFormSubmission, bool aSendCSPViolationEvents, + const OriginAttributes& aOriginAttributes, + RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects, + RedirectHistoryArray&& aRedirectChain, + nsTArray>&& aAncestorPrincipals, + const nsTArray& aAncestorBrowsingContextIDs, + const nsTArray& aCorsUnsafeHeaders, bool aForcePreflight, + bool aIsPreflight, bool aLoadTriggeredFromExternal, + bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted, + bool aAllowListFutureDocumentsCreatedFromThisRedirectChain, + bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce, + bool aSkipContentSniffing, uint32_t aHttpsOnlyStatus, bool aHstsStatus, + bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests, + bool aIsInDevToolsContext, bool aParserCreatedScript, + nsILoadInfo::StoragePermissionState aStoragePermission, + bool aIsMetaRefresh, uint32_t aRequestBlockingReason, + nsINode* aLoadingContext, + nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, + nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo, + bool aHasInjectedCookieForCookieBannerHandling); + LoadInfo(const LoadInfo& rhs); + + NS_IMETHOD GetRedirects(JSContext* aCx, + JS::MutableHandle aRedirects, + const RedirectHistoryArray& aArra); + + friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo( + const Maybe& aLoadInfoArgs, + const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext, + net::LoadInfo** outLoadInfo); + + ~LoadInfo(); + + void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow); + void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal); + + // This function is the *only* function which can change the securityflags + // of a loadinfo. It only exists because of the XHR code. Don't call it + // from anywhere else! + void SetIncludeCookiesSecFlag(); + friend class mozilla::dom::XMLHttpRequestMainThread; + + // nsDocShell::OpenInitializedChannel needs to update the loadInfo with + // the correct browsingContext. + friend class ::nsDocShell; + void UpdateBrowsingContextID(uint64_t aBrowsingContextID) { + mBrowsingContextID = aBrowsingContextID; + } + void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) { + mFrameBrowsingContextID = aFrameBrowsingContextID; + } + MOZ_NEVER_INLINE void ReleaseMembers(); + + // if you add a member, please also update the copy constructor and consider + // if it should be merged from parent channel through + // ParentLoadInfoForwarderArgs. + nsCOMPtr mLoadingPrincipal; + nsCOMPtr mTriggeringPrincipal; + nsCOMPtr mPrincipalToInherit; + nsCOMPtr mTopLevelPrincipal; + nsCOMPtr mResultPrincipalURI; + nsCOMPtr mChannelCreationOriginalURI; + nsCOMPtr mCSPEventListener; + nsCOMPtr mCookieJarSettings; + nsCOMPtr mCspToInherit; + nsCString mTriggeringRemoteType; + nsID mSandboxedNullPrincipalID; + + Maybe mClientInfo; + UniquePtr mReservedClientSource; + Maybe mReservedClientInfo; + Maybe mInitialClientInfo; + Maybe mController; + RefPtr mPerformanceStorage; + + nsWeakPtr mLoadingContext; + nsWeakPtr mContextForTopLevelLoad; + nsSecurityFlags mSecurityFlags; + uint32_t mSandboxFlags; + uint32_t mTriggeringSandboxFlags = 0; + nsContentPolicyType mInternalContentPolicyType; + LoadTainting mTainting = LoadTainting::Basic; + bool mBlockAllMixedContent = false; + bool mUpgradeInsecureRequests = false; + bool mBrowserUpgradeInsecureRequests = false; + bool mBrowserDidUpgradeInsecureRequests = false; + bool mBrowserWouldUpgradeInsecureRequests = false; + bool mForceAllowDataURI = false; + bool mAllowInsecureRedirectToDataURI = false; + bool mSkipContentPolicyCheckForWebRequest = false; + bool mOriginalFrameSrcLoad = false; + bool mForceInheritPrincipalDropped = false; + uint64_t mInnerWindowID = 0; + uint64_t mBrowsingContextID = 0; + uint64_t mWorkerAssociatedBrowsingContextID = 0; + uint64_t mFrameBrowsingContextID = 0; + bool mInitialSecurityCheckDone = false; + // NB: TYPE_DOCUMENT implies !third-party. + bool mIsThirdPartyContext = false; + Maybe mIsThirdPartyContextToTopWindow; + bool mIsFormSubmission = false; + bool mSendCSPViolationEvents = true; + OriginAttributes mOriginAttributes; + RedirectHistoryArray mRedirectChainIncludingInternalRedirects; + RedirectHistoryArray mRedirectChain; + nsTArray> mAncestorPrincipals; + nsTArray mAncestorBrowsingContextIDs; + nsTArray mCorsUnsafeHeaders; + uint32_t mRequestBlockingReason = BLOCKING_REASON_NONE; + bool mForcePreflight = false; + bool mIsPreflight = false; + bool mLoadTriggeredFromExternal = false; + bool mServiceWorkerTaintingSynthesized = false; + bool mDocumentHasUserInteracted = false; + bool mAllowListFutureDocumentsCreatedFromThisRedirectChain = false; + bool mNeedForCheckingAntiTrackingHeuristic = false; + nsString mCspNonce; + bool mSkipContentSniffing = false; + uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED; + bool mHstsStatus = false; + bool mHasValidUserGestureActivation = false; + bool mAllowDeprecatedSystemRequests = false; + bool mIsUserTriggeredSave = false; + bool mIsInDevToolsContext = false; + bool mParserCreatedScript = false; + nsILoadInfo::StoragePermissionState mStoragePermission = + nsILoadInfo::NoStoragePermission; + bool mIsMetaRefresh = false; + + // Is true if this load was triggered by processing the attributes of the + // browsing context container. + // See nsILoadInfo.isFromProcessingFrameAttributes + bool mIsFromProcessingFrameAttributes = false; + + // See nsILoadInfo.isMediaRequest and nsILoadInfo.isMediaInitialRequest. + bool mIsMediaRequest = false; + bool mIsMediaInitialRequest = false; + + // See nsILoadInfo.isFromObjectOrEmbed + bool mIsFromObjectOrEmbed = false; + + bool mSkipCheckForBrokenURLOrZeroSized = false; + + // The cross origin embedder policy that the loading need to respect. + // If the value is nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP, CORP checking + // must be performed for the loading. + // See https://wicg.github.io/cross-origin-embedder-policy/#corp-check. + nsILoadInfo::CrossOriginEmbedderPolicy mLoadingEmbedderPolicy = + nsILoadInfo::EMBEDDER_POLICY_NULL; + + bool mIsOriginTrialCoepCredentiallessEnabledForTopLevel = false; + + nsCOMPtr mUnstrippedURI; + + nsCOMPtr mInterceptionInfo; + + bool mHasInjectedCookieForCookieBannerHandling = false; +}; + +// This is exposed solely for testing purposes and should not be used outside of +// LoadInfo +already_AddRefed CreateTruncatedPrincipal(nsIPrincipal*); + +} // namespace net +} // namespace mozilla + +#endif // mozilla_LoadInfo_h diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h new file mode 100644 index 0000000000..11dc8a304f --- /dev/null +++ b/netwerk/base/LoadTainting.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoadTainting_h +#define mozilla_LoadTainting_h + +#include + +namespace mozilla { + +// Define an enumeration to reflect the concept of response tainting from the +// the fetch spec: +// +// https://fetch.spec.whatwg.org/#concept-request-response-tainting +// +// Roughly the tainting means: +// +// * Basic: the request resulted in a same-origin or non-http load +// * CORS: the request resulted in a cross-origin load with CORS headers +// * Opaque: the request resulted in a cross-origin load without CORS headers +// +// The enumeration is purposefully designed such that more restrictive tainting +// corresponds to a higher integral value. +enum class LoadTainting : uint8_t { Basic = 0, CORS = 1, Opaque = 2 }; + +} // namespace mozilla + +#endif // mozilla_LoadTainting_h diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp new file mode 100644 index 0000000000..afb4552cca --- /dev/null +++ b/netwerk/base/MemoryDownloader.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryDownloader.h" + +#include "mozilla/Assertions.h" +#include "nsIInputStream.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(MemoryDownloader, nsIStreamListener, nsIRequestObserver) + +MemoryDownloader::MemoryDownloader(IObserver* aObserver) + : mObserver(aObserver), mStatus(NS_ERROR_NOT_INITIALIZED) {} + +NS_IMETHODIMP +MemoryDownloader::OnStartRequest(nsIRequest* aRequest) { + MOZ_ASSERT(!mData); + mData.reset(new FallibleTArray()); + mStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { + MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus)); + MOZ_ASSERT(!mData == NS_FAILED(mStatus)); + Data data; + data.swap(mData); + RefPtr observer; + observer.swap(mObserver); + observer->OnDownloadComplete(this, aRequest, aStatus, std::move(data)); + return NS_OK; +} + +nsresult MemoryDownloader::ConsumeData(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + MemoryDownloader* self = static_cast(aClosure); + if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) { + // The error returned by ConsumeData isn't propagated to the + // return of ReadSegments, so it has to be passed as state. + self->mStatus = NS_ERROR_OUT_OF_MEMORY; + return NS_ERROR_OUT_OF_MEMORY; + } + *aWriteCount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr, + uint64_t aSourceOffset, uint32_t aCount) { + uint32_t n; + MOZ_ASSERT(mData); + nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n); + if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) { + mStatus = rv; + } + if (NS_WARN_IF(NS_FAILED(mStatus))) { + mData.reset(nullptr); + return mStatus; + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h new file mode 100644 index 0000000000..8b5700f5bf --- /dev/null +++ b/netwerk/base/MemoryDownloader.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_MemoryDownloader_h__ +#define mozilla_net_MemoryDownloader_h__ + +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsTArray.h" + +/** + * mozilla::net::MemoryDownloader + * + * This class is similar to nsIDownloader, but stores the downloaded + * stream in memory instead of a file. Ownership of the temporary + * memory is transferred to the observer when download is complete; + * there is no need to retain a reference to the downloader. + */ + +namespace mozilla { +namespace net { + +class MemoryDownloader final : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + using Data = mozilla::UniquePtr>; + + class IObserver : public nsISupports { + public: + // Note: aData may be null if (and only if) aStatus indicates failure. + virtual void OnDownloadComplete(MemoryDownloader* aDownloader, + nsIRequest* aRequest, nsresult aStatus, + Data aData) = 0; + }; + + explicit MemoryDownloader(IObserver* aObserver); + + private: + virtual ~MemoryDownloader() = default; + + static nsresult ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, uint32_t toOffset, + uint32_t count, uint32_t* writeCount); + + RefPtr mObserver; + Data mData; + nsresult mStatus; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_MemoryDownloader_h__ diff --git a/netwerk/base/NetUtil.sys.mjs b/netwerk/base/NetUtil.sys.mjs new file mode 100644 index 0000000000..679c9979a7 --- /dev/null +++ b/netwerk/base/NetUtil.sys.mjs @@ -0,0 +1,453 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Necko utilities + */ + +// ////////////////////////////////////////////////////////////////////////////// +// // Constants + +const PR_UINT32_MAX = 0xffffffff; + +const BinaryInputStream = Components.Constructor( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +// ////////////////////////////////////////////////////////////////////////////// +// // NetUtil Object + +export var NetUtil = { + /** + * Function to perform simple async copying from aSource (an input stream) + * to aSink (an output stream). The copy will happen on some background + * thread. Both streams will be closed when the copy completes. + * + * @param aSource + * The input stream to read from + * @param aSink + * The output stream to write to + * @param aCallback [optional] + * A function that will be called at copy completion with a single + * argument: the nsresult status code for the copy operation. + * + * @return An nsIRequest representing the copy operation (for example, this + * can be used to cancel the copying). The consumer can ignore the + * return value if desired. + */ + asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) { + if (!aSource || !aSink) { + let exception = new Components.Exception( + "Must have a source and a sink", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // make a stream copier + var copier = Cc[ + "@mozilla.org/network/async-stream-copier;1" + ].createInstance(Ci.nsIAsyncStreamCopier2); + copier.init( + aSource, + aSink, + null /* Default event target */, + 0 /* Default length */, + true, + true /* Auto-close */ + ); + + var observer; + if (aCallback) { + observer = { + onStartRequest(aRequest) {}, + onStopRequest(aRequest, aStatusCode) { + aCallback(aStatusCode); + }, + }; + } else { + observer = null; + } + + // start the copying + copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null); + return copier; + }, + + /** + * Asynchronously opens a source and fetches the response. While the fetch + * is asynchronous, I/O may happen on the main thread. When reading from + * a local file, prefer using IOUtils methods instead. + * + * @param aSource + * This argument can be one of the following: + * - An options object that will be passed to NetUtil.newChannel. + * - An existing nsIChannel. + * - An existing nsIInputStream. + * Using an nsIURI, nsIFile, or string spec directly is deprecated. + * @param aCallback + * The callback function that will be notified upon completion. It + * will get these arguments: + * 1) An nsIInputStream containing the data from aSource, if any. + * 2) The status code from opening the source. + * 3) Reference to the nsIRequest. + */ + asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) { + if (!aSource || !aCallback) { + let exception = new Components.Exception( + "Must have a source and a callback", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // Create a pipe that will create our output stream that we can use once + // we have gotten all the data. + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, PR_UINT32_MAX, null); + + // Create a listener that will give data to the pipe's output stream. + let listener = Cc[ + "@mozilla.org/network/simple-stream-listener;1" + ].createInstance(Ci.nsISimpleStreamListener); + listener.init(pipe.outputStream, { + onStartRequest(aRequest) {}, + onStopRequest(aRequest, aStatusCode) { + pipe.outputStream.close(); + aCallback(pipe.inputStream, aStatusCode, aRequest); + }, + }); + + // Input streams are handled slightly differently from everything else. + if (aSource instanceof Ci.nsIInputStream) { + let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance( + Ci.nsIInputStreamPump + ); + pump.init(aSource, 0, 0, true); + pump.asyncRead(listener, null); + return; + } + + let channel = aSource; + if (!(channel instanceof Ci.nsIChannel)) { + channel = this.newChannel(aSource); + } + + try { + channel.asyncOpen(listener); + } catch (e) { + let exception = new Components.Exception( + "Failed to open input source '" + channel.originalURI.spec + "'", + e.result, + Components.stack.caller, + aSource, + e + ); + throw exception; + } + }, + + /** + * Constructs a new URI for the given spec, character set, and base URI, or + * an nsIFile. + * + * @param aTarget + * The string spec for the desired URI or an nsIFile. + * @param aOriginCharset [optional] + * The character set for the URI. Only used if aTarget is not an + * nsIFile. + * @param aBaseURI [optional] + * The base URI for the spec. Only used if aTarget is not an + * nsIFile. + * + * @return an nsIURI object. + */ + newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) { + if (!aTarget) { + let exception = new Components.Exception( + "Must have a non-null string spec or nsIFile object", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aTarget instanceof Ci.nsIFile) { + return Services.io.newFileURI(aTarget); + } + + return Services.io.newURI(aTarget, aOriginCharset, aBaseURI); + }, + + /** + * Constructs a new channel for the given source. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + * + * @param aWhatToLoad + * This argument used to be a string spec for the desired URI, an + * nsIURI, or an nsIFile. Now it should be an options object with + * the following properties: + * { + * uri: + * The full URI spec string, nsIURI or nsIFile to create the + * channel for. + * Note that this cannot be an nsIFile if you have to specify a + * non-default charset or base URI. Call NetUtil.newURI first if + * you need to construct an URI using those options. + * loadingNode: + * loadingPrincipal: + * triggeringPrincipal: + * securityFlags: + * contentPolicyType: + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * loadUsingSystemPrincipal: + * Set this to true to use the system principal as + * loadingPrincipal. This must be omitted if loadingPrincipal or + * loadingNode are present. + * This should be used with care as it skips security checks. + * } + * @return an nsIChannel object. + */ + newChannel: function NetUtil_newChannel(aWhatToLoad) { + // Make sure the API is called using only the options object. + if (typeof aWhatToLoad != "object" || arguments.length != 1) { + throw new Components.Exception( + "newChannel requires a single object argument", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + let { + uri, + loadingNode, + loadingPrincipal, + loadUsingSystemPrincipal, + triggeringPrincipal, + securityFlags, + contentPolicyType, + } = aWhatToLoad; + + if (!uri) { + throw new Components.Exception( + "newChannel requires the 'uri' property on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (typeof uri == "string" || uri instanceof Ci.nsIFile) { + uri = this.newURI(uri); + } + + if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires at least one of the 'loadingNode'," + + " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" + + " properties on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (loadUsingSystemPrincipal === true) { + if (loadingNode || loadingPrincipal) { + throw new Components.Exception( + "newChannel does not accept 'loadUsingSystemPrincipal'" + + " if the 'loadingNode' or 'loadingPrincipal' properties" + + " are present on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + } else if (loadUsingSystemPrincipal !== undefined) { + throw new Components.Exception( + "newChannel requires the 'loadUsingSystemPrincipal'" + + " property on the options object to be 'true' or 'undefined'.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (securityFlags === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'securityFlags' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + } + + if (contentPolicyType === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'contentPolicyType' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; + } + + let channel = Services.io.newChannelFromURI( + uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType + ); + if (loadUsingSystemPrincipal) { + channel.loadInfo.allowDeprecatedSystemRequests = true; + } + return channel; + }, + + newWebTransport: function NetUtil_newWebTransport() { + return Services.io.newWebTransport(); + }, + + /** + * Reads aCount bytes from aInputStream into a string. + * + * @param aInputStream + * The input stream to read from. + * @param aCount + * The number of bytes to read from the stream. + * @param aOptions [optional] + * charset + * The character encoding of stream data. + * replacement + * The character to replace unknown byte sequences. + * If unset, it causes an exceptions to be thrown. + * + * @return the bytes from the input stream in string form. + * + * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would + * block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences + */ + readInputStreamToString: function NetUtil_readInputStreamToString( + aInputStream, + aCount, + aOptions + ) { + if (!(aInputStream instanceof Ci.nsIInputStream)) { + let exception = new Components.Exception( + "First argument should be an nsIInputStream", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (!aCount) { + let exception = new Components.Exception( + "Non-zero amount of bytes must be specified", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aOptions && "charset" in aOptions) { + let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance( + Ci.nsIConverterInputStream + ); + try { + // When replacement is set, the character that is unknown sequence + // replaces with aOptions.replacement character. + if (!("replacement" in aOptions)) { + // aOptions.replacement isn't set. + // If input stream has unknown sequences for aOptions.charset, + // throw NS_ERROR_ILLEGAL_INPUT. + aOptions.replacement = 0; + } + + cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement); + let str = {}; + cis.readString(-1, str); + cis.close(); + return str.value; + } catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception( + e.message, + e.result, + Components.stack.caller, + e.data + ); + } + } + + let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(aInputStream); + try { + return sis.readBytes(aCount); + } catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception( + e.message, + e.result, + Components.stack.caller, + e.data + ); + } + }, + + /** + * Reads aCount bytes from aInputStream into a string. + * + * @param {nsIInputStream} aInputStream + * The input stream to read from. + * @param {integer} [aCount = aInputStream.available()] + * The number of bytes to read from the stream. + * + * @return the bytes from the input stream in string form. + * + * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would + * block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + */ + readInputStream(aInputStream, aCount) { + if (!(aInputStream instanceof Ci.nsIInputStream)) { + let exception = new Components.Exception( + "First argument should be an nsIInputStream", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (!aCount) { + aCount = aInputStream.available(); + } + + let stream = new BinaryInputStream(aInputStream); + let result = new ArrayBuffer(aCount); + stream.readArrayBuffer(result.byteLength, result); + return result; + }, +}; diff --git a/netwerk/base/NetworkConnectivityService.cpp b/netwerk/base/NetworkConnectivityService.cpp new file mode 100644 index 0000000000..f68728db7b --- /dev/null +++ b/netwerk/base/NetworkConnectivityService.cpp @@ -0,0 +1,552 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DNSUtils.h" +#include "NetworkConnectivityService.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsIOService.h" +#include "nsICancelable.h" +#include "xpcpublic.h" +#include "nsSocketTransport2.h" +#include "nsIHttpChannelInternal.h" +#include "nsINetworkLinkService.h" +#include "mozilla/StaticPrefs_network.h" + +static mozilla::LazyLogModule gNCSLog("NetworkConnectivityService"); +#undef LOG +#define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver, + nsINetworkConnectivityService, nsIStreamListener) + +static StaticRefPtr gConnService; + +NetworkConnectivityService::NetworkConnectivityService() + : mDNSv4(UNKNOWN), + mDNSv6(UNKNOWN), + mIPv4(UNKNOWN), + mIPv6(UNKNOWN), + mNAT64(UNKNOWN), + mLock("nat64prefixes") {} + +// static +already_AddRefed +NetworkConnectivityService::GetSingleton() { + if (gConnService) { + return do_AddRef(gConnService); + } + + RefPtr service = new NetworkConnectivityService(); + service->Init(); + + gConnService = std::move(service); + ClearOnShutdown(&gConnService); + return do_AddRef(gConnService); +} + +nsresult NetworkConnectivityService::Init() { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); + observerService->AddObserver(this, "network:captive-portal-connectivity", + false); + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mDNSv4; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mDNSv6; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetIPv4(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mIPv4; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetIPv6(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mIPv6; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetNAT64(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mNAT64; + return NS_OK; +} + +already_AddRefed NetworkConnectivityService::MapNAT64IPs( + AddrInfo* aNewRRSet) { + // Add prefixes only if there are no IPv6 addresses. + // Expect that if aNewRRSet has IPv6 addresses, they must come + // before IPv4 addresses. + if (aNewRRSet->Addresses().IsEmpty() || + aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) { + return do_AddRef(aNewRRSet); + } + + // Currently we only add prefixes to the first IP's clones. + uint32_t ip = aNewRRSet->Addresses()[0].inet.ip; + nsTArray addresses = aNewRRSet->Addresses().Clone(); + + { + MutexAutoLock lock(mLock); + for (const auto& prefix : mNAT64Prefixes) { + NetAddr addr = NetAddr(prefix); + + // Copy the IPv4 address to the end + addr.inet6.ip.u32[3] = ip; + + // If we have both IPv4 and NAT64, we be could insourcing NAT64 + // to avoid double NAT and improve performance. However, this + // breaks WebRTC, so we push it to the back. + addresses.AppendElement(addr); + } + } + + auto builder = aNewRRSet->Build(); + builder.SetAddresses(std::move(addresses)); + return builder.Finish(); +} + +// Returns true if a prefix was read and saved to the argument +static inline bool NAT64PrefixFromPref(NetAddr* prefix) { + nsAutoCString nat64PrefixPref; + + nsresult rv = Preferences::GetCString( + "network.connectivity-service.nat64-prefix", nat64PrefixPref); + return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() || + NS_FAILED(prefix->InitFromString(nat64PrefixPref)) || + prefix->raw.family != PR_AF_INET6); +} + +static inline bool NAT64PrefixCompare(const NetAddr& prefix1, + const NetAddr& prefix2) { + // Compare the first 96 bits as 64 + 32 + return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] && + prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2]; +} + +void NetworkConnectivityService::PerformChecks() { + mDNSv4 = UNKNOWN; + mDNSv6 = UNKNOWN; + + mIPv4 = UNKNOWN; + mIPv6 = UNKNOWN; + + mNAT64 = UNKNOWN; + + { + MutexAutoLock lock(mLock); + mNAT64Prefixes.Clear(); + + // NAT64 checks might be disabled. + // Since We can't guarantee a DNS response, we should set up + // NAT64 manually now if needed. + + NetAddr priorityPrefix{}; + bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); + if (havePrefix) { + mNAT64Prefixes.AppendElement(priorityPrefix); + mNAT64 = OK; + } + } + + RecheckDNS(); + RecheckIPConnectivity(); +} + +static inline void NotifyObservers(const char* aTopic) { + nsCOMPtr obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, aTopic, nullptr); +} + +void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) { + nsCOMPtr rec = do_QueryInterface(aRecord); + MutexAutoLock lock(mLock); + mNAT64Prefixes.Clear(); + + NetAddr priorityPrefix{}; + bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); + if (havePrefix) { + mNAT64 = OK; + mNAT64Prefixes.AppendElement(priorityPrefix); + } + + if (!rec) { + if (!havePrefix) { + mNAT64 = NOT_AVAILABLE; + } + return; + } + + mNAT64 = UNKNOWN; + NetAddr addr{}; + + // use port 80 as dummy value for NetAddr + while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) { + if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) { + // These are not the kind of addresses we are looking for. + continue; + } + + // RFC 7050 does not require the embedded IPv4 to be + // at the end of IPv6. In practice, and as we assume, + // it is always at the end. + // The embedded IP must be 192.0.0.170 or 192.0.0.171 + + // Clear the last bit to compare with the next one. + addr.inet6.ip.u8[15] &= ~(uint32_t)1; + if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) || + (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) { + continue; + } + + mNAT64Prefixes.AppendElement(addr); + } + + size_t length = mNAT64Prefixes.Length(); + if (length == 0) { + mNAT64 = NOT_AVAILABLE; + return; + } + + // Remove duplicates. Typically a DNS64 resolver sends every + // prefix twice with address with different last bits. We want + // a list of unique prefixes while reordering is not allowed. + // We must not handle the case with an element in-between + // two identical ones, which is never the case for a properly + // configured DNS64 resolver. + + NetAddr prev = mNAT64Prefixes[0]; + + for (size_t i = 1; i < length; i++) { + if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) { + mNAT64Prefixes.RemoveElementAt(i); + i--; + length--; + } else { + prev = mNAT64Prefixes[i]; + } + } + + // The prioritized address might also appear in the record we received. + + if (havePrefix) { + for (size_t i = 1; i < length; i++) { + if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) { + mNAT64Prefixes.RemoveElementAt(i); + // It wouldn't appear more than once. + break; + } + } + } + + mNAT64 = OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest, + nsIDNSRecord* aRecord, + nsresult aStatus) { + ConnectivityState state = NS_SUCCEEDED(aStatus) ? OK : NOT_AVAILABLE; + + if (aRequest == mDNSv4Request) { + mDNSv4 = state; + mDNSv4Request = nullptr; + } else if (aRequest == mDNSv6Request) { + mDNSv6 = state; + mDNSv6Request = nullptr; + } else if (aRequest == mNAT64Request) { + mNAT64Request = nullptr; + SaveNAT64Prefixes(aRecord); + } + + if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) { + NotifyObservers("network:connectivity-service:dns-checks-complete"); + } + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::RecheckDNS() { + bool enabled = + Preferences::GetBool("network.connectivity-service.enabled", false); + if (!enabled) { + return NS_OK; + } + + if (nsIOService::UseSocketProcess()) { + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (parent) { + Unused << parent->SendRecheckDNS(); + } + } + + nsresult rv; + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + OriginAttributes attrs; + nsAutoCString host; + Preferences::GetCString("network.connectivity-service.DNSv4.domain", host); + + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV6 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mDNSv4Request)); + NS_ENSURE_SUCCESS(rv, rv); + + Preferences::GetCString("network.connectivity-service.DNSv6.domain", host); + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV4 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mDNSv6Request)); + NS_ENSURE_SUCCESS(rv, rv); + + if (StaticPrefs::network_connectivity_service_nat64_check()) { + rv = dns->AsyncResolveNative("ipv4only.arpa"_ns, + nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV4 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mNAT64Request)); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; +} + +NS_IMETHODIMP +NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "network:captive-portal-connectivity")) { + // Captive portal is cleared, so we redo the checks. + PerformChecks(); + } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + if (mDNSv4Request) { + mDNSv4Request->Cancel(NS_ERROR_ABORT); + mDNSv4Request = nullptr; + } + if (mDNSv6Request) { + mDNSv6Request->Cancel(NS_ERROR_ABORT); + mDNSv6Request = nullptr; + } + if (mNAT64Request) { + mNAT64Request->Cancel(NS_ERROR_ABORT); + mNAT64Request = nullptr; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + observerService->RemoveObserver(this, + "network:captive-portal-connectivity"); + observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); + } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && + !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN) + .Equals(aData)) { + PerformChecks(); + } + + return NS_OK; +} + +already_AddRefed NetworkConnectivityService::SetupIPCheckChannel( + bool ipv4) { + nsresult rv; + nsAutoCString url; + + if (ipv4) { + rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url); + } else { + rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url); + } + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), url); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr channel; + if (XRE_IsSocketProcess()) { + rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel)); + if (NS_FAILED(rv)) { + return nullptr; + } + channel->SetLoadFlags( + nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache + nsIRequest::INHIBIT_CACHING | // don't write the response to cache + nsIRequest::LOAD_ANONYMOUS); + } else { + rv = NS_NewChannel( + getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + nullptr, + nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache + nsIRequest::INHIBIT_CACHING | // don't write the response to cache + nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks + NS_ENSURE_SUCCESS(rv, nullptr); + + { + // Prevent HTTPS-Only Mode from upgrading the OCSP request. + nsCOMPtr loadInfo = channel->LoadInfo(); + uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); + httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT; + loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); + + // allow deprecated HTTP request from SystemPrincipal + loadInfo->SetAllowDeprecatedSystemRequests(true); + } + } + + rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr internalChan = do_QueryInterface(channel); + NS_ENSURE_TRUE(internalChan, nullptr); + + if (ipv4) { + internalChan->SetIPv6Disabled(); + } else { + internalChan->SetIPv4Disabled(); + } + + return channel.forget(); +} + +NS_IMETHODIMP +NetworkConnectivityService::RecheckIPConnectivity() { + bool enabled = + Preferences::GetBool("network.connectivity-service.enabled", false); + if (!enabled) { + return NS_OK; + } + + if (nsIOService::UseSocketProcess()) { + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (parent) { + Unused << parent->SendRecheckIPConnectivity(); + } + } + + if (xpc::AreNonLocalConnectionsDisabled() && + !Preferences::GetBool("network.captive-portal-service.testMode", false)) { + return NS_OK; + } + + if (mIPv4Channel) { + mIPv4Channel->Cancel(NS_ERROR_ABORT); + mIPv4Channel = nullptr; + } + if (mIPv6Channel) { + mIPv6Channel->Cancel(NS_ERROR_ABORT); + mIPv6Channel = nullptr; + } + + nsresult rv; + mHasNetworkId = false; + mCheckedNetworkId = false; + mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true); + if (mIPv4Channel) { + rv = mIPv4Channel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false); + if (mIPv6Channel) { + rv = mIPv6Channel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) { + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + if (aStatusCode == NS_ERROR_ABORT) { + return NS_OK; + } + + ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK; + + if (aRequest == mIPv4Channel) { + mIPv4 = status; + mIPv4Channel = nullptr; + + if (mIPv4 == nsINetworkConnectivityService::OK) { + Telemetry::AccumulateCategorical( + mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present + : Telemetry::LABELS_NETWORK_ID_ONLINE::absent); + LOG(("mHasNetworkId : %d\n", mHasNetworkId)); + } + } else if (aRequest == mIPv6Channel) { + mIPv6 = status; + mIPv6Channel = nullptr; + } + + if (!mIPv6Channel && !mIPv4Channel) { + NotifyObservers("network:connectivity-service:ip-checks-complete"); + } + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + nsAutoCString data; + + // We perform this check here, instead of doing it in OnStopRequest in case + // a network down event occurs after the data has arrived but before we fire + // OnStopRequest. That would cause us to report a missing networkID, even + // though it was not empty while receiving data. + if (aRequest == mIPv4Channel && !mCheckedNetworkId) { + nsCOMPtr nls = + do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID); + nsAutoCString networkId; + if (nls) { + nls->GetNetworkID(networkId); + } + mHasNetworkId = !networkId.IsEmpty(); + mCheckedNetworkId = true; + } + + Unused << NS_ReadInputStreamToString(aInputStream, data, aCount); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkConnectivityService.h b/netwerk/base/NetworkConnectivityService.h new file mode 100644 index 0000000000..6315fb192b --- /dev/null +++ b/netwerk/base/NetworkConnectivityService.h @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetworkConnectivityService_h_ +#define NetworkConnectivityService_h_ + +#include "nsINetworkConnectivityService.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIDNSListener.h" +#include "nsIStreamListener.h" +#include "mozilla/net/DNS.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +class NetworkConnectivityService : public nsINetworkConnectivityService, + public nsIObserver, + public nsIDNSListener, + public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKCONNECTIVITYSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSIDNSLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + already_AddRefed MapNAT64IPs(AddrInfo* aNewRRSet); + + static already_AddRefed GetSingleton(); + + private: + NetworkConnectivityService(); + virtual ~NetworkConnectivityService() = default; + + nsresult Init(); + // Calls all the check methods + void PerformChecks(); + + void SaveNAT64Prefixes(nsIDNSRecord* aRecord); + + already_AddRefed SetupIPCheckChannel(bool ipv4); + + // Will be set to OK if the DNS request returned in IP of this type, + // NOT_AVAILABLE if that type of resolution is not available + // UNKNOWN if the check wasn't performed + Atomic mDNSv4; + Atomic mDNSv6; + + Atomic mIPv4; + Atomic mIPv6; + + Atomic mNAT64; + + nsTArray mNAT64Prefixes; + + nsCOMPtr mDNSv4Request; + nsCOMPtr mDNSv6Request; + nsCOMPtr mNAT64Request; + + nsCOMPtr mIPv4Channel; + nsCOMPtr mIPv6Channel; + + bool mCheckedNetworkId = false; + bool mHasNetworkId = false; + + Mutex mLock MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif // NetworkConnectivityService_h_ diff --git a/netwerk/base/NetworkDataCountLayer.cpp b/netwerk/base/NetworkDataCountLayer.cpp new file mode 100644 index 0000000000..bee8bd1ee5 --- /dev/null +++ b/netwerk/base/NetworkDataCountLayer.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NetworkDataCountLayer.h" +#include "nsSocketTransportService2.h" +#include "prmem.h" +#include "prio.h" + +namespace mozilla { +namespace net { + +static PRDescIdentity sNetworkDataCountLayerIdentity; +static PRIOMethods sNetworkDataCountLayerMethods; +static PRIOMethods* sNetworkDataCountLayerMethodsPtr = nullptr; + +class NetworkDataCountSecret { + public: + NetworkDataCountSecret() = default; + + uint64_t mSentBytes = 0; + uint64_t mReceivedBytes = 0; +}; + +static PRInt32 NetworkDataCountSend(PRFileDesc* fd, const void* buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity); + + NetworkDataCountSecret* secret = + reinterpret_cast(fd->secret); + + PRInt32 rv = + (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout); + if (rv > 0) { + secret->mSentBytes += rv; + } + return rv; +} + +static PRInt32 NetworkDataCountWrite(PRFileDesc* fd, const void* buf, + PRInt32 amount) { + return NetworkDataCountSend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRInt32 NetworkDataCountRecv(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity); + + NetworkDataCountSecret* secret = + reinterpret_cast(fd->secret); + + PRInt32 rv = + (fd->lower->methods->recv)(fd->lower, buf, amount, flags, timeout); + if (rv > 0) { + secret->mReceivedBytes += rv; + } + return rv; +} + +static PRInt32 NetworkDataCountRead(PRFileDesc* fd, void* buf, PRInt32 amount) { + return NetworkDataCountRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRStatus NetworkDataCountClose(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + + MOZ_RELEASE_ASSERT(layer && layer->identity == sNetworkDataCountLayerIdentity, + "NetworkDataCount Layer not on top of stack"); + + NetworkDataCountSecret* secret = + reinterpret_cast(layer->secret); + layer->secret = nullptr; + layer->dtor(layer); + delete secret; + return fd->methods->close(fd); +} + +nsresult AttachNetworkDataCountLayer(PRFileDesc* fd) { + if (!sNetworkDataCountLayerMethodsPtr) { + sNetworkDataCountLayerIdentity = + PR_GetUniqueIdentity("NetworkDataCount Layer"); + sNetworkDataCountLayerMethods = *PR_GetDefaultIOMethods(); + sNetworkDataCountLayerMethods.send = NetworkDataCountSend; + sNetworkDataCountLayerMethods.write = NetworkDataCountWrite; + sNetworkDataCountLayerMethods.recv = NetworkDataCountRecv; + sNetworkDataCountLayerMethods.read = NetworkDataCountRead; + sNetworkDataCountLayerMethods.close = NetworkDataCountClose; + sNetworkDataCountLayerMethodsPtr = &sNetworkDataCountLayerMethods; + } + + PRFileDesc* layer = PR_CreateIOLayerStub(sNetworkDataCountLayerIdentity, + sNetworkDataCountLayerMethodsPtr); + + if (!layer) { + return NS_ERROR_FAILURE; + } + + NetworkDataCountSecret* secret = new NetworkDataCountSecret(); + + layer->secret = reinterpret_cast(secret); + + PRStatus status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes) { + PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity); + MOZ_RELEASE_ASSERT(ndcFd); + + NetworkDataCountSecret* secret = + reinterpret_cast(ndcFd->secret); + sentBytes = secret->mSentBytes; +} + +void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes) { + PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity); + MOZ_RELEASE_ASSERT(ndcFd); + + NetworkDataCountSecret* secret = + reinterpret_cast(ndcFd->secret); + receivedBytes = secret->mReceivedBytes; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkDataCountLayer.h b/netwerk/base/NetworkDataCountLayer.h new file mode 100644 index 0000000000..a5a0d8f0dc --- /dev/null +++ b/netwerk/base/NetworkDataCountLayer.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetworkDataCountLayer_h__ +#define NetworkDataCountLayer_h__ + +#include "prerror.h" +#include "prio.h" +#include "ErrorList.h" + +namespace mozilla { +namespace net { + +nsresult AttachNetworkDataCountLayer(PRFileDesc* fd); + +// Get amount of sent bytes. +void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes); + +// Get amount of received bytes. +void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes); + +} // namespace net +} // namespace mozilla + +#endif // NetworkDataCountLayer_h__ diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp new file mode 100644 index 0000000000..554557c809 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceCocoa.cpp @@ -0,0 +1,100 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include + +#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 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), + nullptr, 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.InsertOrUpdate(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 0000000000..218f31cb0c --- /dev/null +++ b/netwerk/base/NetworkInfoServiceImpl.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsTHashMap.h" + +namespace mozilla { +namespace net { + +using AddrMapType = nsTHashMap; + +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 0000000000..8b365e4028 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceLinux.cpp @@ -0,0 +1,95 @@ +/* -*- 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 +#include +#include +#include +#include +#include + +#include "mozilla/DebugOnly.h" +#include "mozilla/ScopeExit.h" + +#include "NetworkInfoServiceImpl.h" + +namespace mozilla::net { + +static nsresult ListInterfaceAddresses(int aFd, const char* aInterface, + 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 rv = + ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed"); + + ifreq = (struct ifreq*)((char*)ifreq + len); + i += len; + } + + 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]; + switch (ifreq.ifr_addr.sa_family) { + case AF_INET: + case AF_INET6: + getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), + nullptr, 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.InsertOrUpdate(ifaceStr, addrStr); + + return NS_OK; +} + +} // namespace mozilla::net diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp new file mode 100644 index 0000000000..943226cd5f --- /dev/null +++ b/netwerk/base/NetworkInfoServiceWindows.cpp @@ -0,0 +1,58 @@ +/* -*- 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 +#include +#include + +#include "mozilla/UniquePtr.h" + +#include "NetworkInfoServiceImpl.h" + +namespace mozilla { +namespace net { + +nsresult DoListAddresses(AddrMapType& aAddrMap) { + UniquePtr 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.InsertOrUpdate(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 0000000000..a99d6d88f5 --- /dev/null +++ b/netwerk/base/PollableEvent.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransportService2.h" +#include "PollableEvent.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/net/DNS.h" +#include "prerror.h" +#include "prio.h" +#include "private/pprio.h" +#include "prnetdb.h" + +#ifdef XP_WIN +# include "ShutdownLayer.h" +#else +# include +# define USEPIPE 1 +#endif + +namespace mozilla { +namespace net { + +#ifndef USEPIPE +static PRDescIdentity sPollableEventLayerIdentity; +static PRIOMethods sPollableEventLayerMethods; +static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr; + +static void LazyInitSocket() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (sPollableEventLayerMethodsPtr) { + return; + } + sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer"); + sPollableEventLayerMethods = *PR_GetDefaultIOMethods(); + sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods; +} + +static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) { + // this is a replacement for PR_NewTCPSocketPair that manually + // sets the recv buffer to 64K. A windows bug (1248358) + // can result in using an incompatible rwin and window + // scale option on localhost pipes if not set before connect. + + SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", + aSetRecvBuff ? "with" : "without")); + + PRFileDesc* listener = nullptr; + PRFileDesc* writer = nullptr; + PRFileDesc* reader = nullptr; + PRSocketOptionData recvBufferOpt; + recvBufferOpt.option = PR_SockOpt_RecvBufferSize; + recvBufferOpt.value.recv_buffer_size = 65535; + + PRSocketOptionData nodelayOpt; + nodelayOpt.option = PR_SockOpt_NoDelay; + nodelayOpt.value.no_delay = true; + + PRSocketOptionData noblockOpt; + noblockOpt.option = PR_SockOpt_Nonblocking; + noblockOpt.value.non_blocking = true; + + listener = PR_OpenTCPSocket(PR_AF_INET); + if (!listener) { + goto failed; + } + + if (aSetRecvBuff) { + PR_SetSocketOption(listener, &recvBufferOpt); + } + PR_SetSocketOption(listener, &nodelayOpt); + + PRNetAddr listenAddr; + memset(&listenAddr, 0, sizeof(listenAddr)); + if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) || + (PR_Bind(listener, &listenAddr) == PR_FAILURE) || + (PR_GetSockName(listener, &listenAddr) == + PR_FAILURE) || // learn the dynamic port + (PR_Listen(listener, 5) == PR_FAILURE)) { + goto failed; + } + + writer = PR_OpenTCPSocket(PR_AF_INET); + if (!writer) { + goto failed; + } + if (aSetRecvBuff) { + PR_SetSocketOption(writer, &recvBufferOpt); + } + PR_SetSocketOption(writer, &nodelayOpt); + PR_SetSocketOption(writer, &noblockOpt); + PRNetAddr writerAddr; + if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), + &writerAddr) == PR_FAILURE) { + goto failed; + } + + if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) { + if ((PR_GetError() != PR_IN_PROGRESS_ERROR) || + (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) { + goto failed; + } + } + PR_SetFDInheritable(writer, false); + + reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); + if (!reader) { + goto failed; + } + PR_SetFDInheritable(reader, false); + if (aSetRecvBuff) { + PR_SetSocketOption(reader, &recvBufferOpt); + } + PR_SetSocketOption(reader, &nodelayOpt); + PR_SetSocketOption(reader, &noblockOpt); + PR_Close(listener); + + fd[0] = reader; + fd[1] = writer; + return true; + +failed: + if (listener) { + PR_Close(listener); + } + if (reader) { + PR_Close(reader); + } + if (writer) { + PR_Close(writer); + } + return false; +} + +#endif + +PollableEvent::PollableEvent() + +{ + MOZ_COUNT_CTOR(PollableEvent); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // create pair of prfiledesc that can be used as a poll()ble + // signal. on windows use a localhost socket pair, and on + // unix use a pipe. +#ifdef USEPIPE + SOCKET_LOG(("PollableEvent() using pipe\n")); + if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) { + // make the pipe non blocking. NSPR asserts at + // trying to use SockOpt here + PROsfd fd = PR_FileDesc2NativeHandle(mReadFD); + int flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + fd = PR_FileDesc2NativeHandle(mWriteFD); + flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } else { + mReadFD = nullptr; + mWriteFD = nullptr; + SOCKET_LOG(("PollableEvent() pipe failed\n")); + } +#else + SOCKET_LOG(("PollableEvent() using socket pair\n")); + PRFileDesc* fd[2]; + LazyInitSocket(); + + // Try with a increased recv buffer first (bug 1248358). + if (NewTCPSocketPair(fd, true)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If the previous fails try without recv buffer increase (bug 1305436). + } else if (NewTCPSocketPair(fd, false)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If both fail, try the old version. + } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + + PRSocketOptionData socket_opt; + DebugOnly status; + socket_opt.option = PR_SockOpt_NoDelay; + socket_opt.value.no_delay = true; + PR_SetSocketOption(mWriteFD, &socket_opt); + PR_SetSocketOption(mReadFD, &socket_opt); + socket_opt.option = PR_SockOpt_Nonblocking; + socket_opt.value.non_blocking = true; + status = PR_SetSocketOption(mWriteFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + status = PR_SetSocketOption(mReadFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + } + + if (mReadFD && mWriteFD) { + // compatibility with LSPs such as McAfee that assume a NSPR + // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a + // nop. + PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity, + sPollableEventLayerMethodsPtr); + if (topLayer) { + if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) { + topLayer->dtor(topLayer); + } else { + SOCKET_LOG(("PollableEvent() nspr layer ok\n")); + mReadFD = topLayer; + } + } + + } else { + SOCKET_LOG(("PollableEvent() socketpair failed\n")); + } +#endif + + if (mReadFD && mWriteFD) { + // prime the system to deal with races invovled in [dc]tor cycle + SOCKET_LOG(("PollableEvent() ctor ok\n")); + mSignaled = true; + MarkFirstSignalTimestamp(); + PR_Write(mWriteFD, "I", 1); + } +} + +PollableEvent::~PollableEvent() { + MOZ_COUNT_DTOR(PollableEvent); + if (mWriteFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mWriteFD); +#endif + PR_Close(mWriteFD); + } + if (mReadFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mReadFD); +#endif + PR_Close(mReadFD); + } +} + +// we do not record signals on the socket thread +// because the socket thread can reliably look at its +// own runnable queue before selecting a poll time +// this is the "service the network without blocking" comment in +// nsSocketTransportService2.cpp +bool PollableEvent::Signal() { + SOCKET_LOG(("PollableEvent::Signal\n")); + + if (!mWriteFD) { + SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n")); + return false; + } +#ifndef XP_WIN + // On windows poll can hang and this became worse when we introduced the + // patch for bug 698882 (see also bug 1292181), therefore we reverted the + // behavior on windows to be as before bug 698882, e.g. write to the socket + // also if an event dispatch is on the socket thread and writing to the + // socket for each event. See bug 1292181. + if (OnSocketThread()) { + SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n")); + return true; + } +#endif + +#ifndef XP_WIN + // To wake up the poll writing once is enough, but for Windows that can cause + // hangs so we will write for every event. + // For non-Windows systems it is enough to write just once. + if (mSignaled) { + return true; + } +#endif + + if (!mSignaled) { + mSignaled = true; + MarkFirstSignalTimestamp(); + } + + int32_t status = PR_Write(mWriteFD, "M", 1); + SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status)); + if (status != 1) { + NS_WARNING("PollableEvent::Signal Failed\n"); + SOCKET_LOG(("PollableEvent::Signal Failed\n")); + mSignaled = false; + mWriteFailed = true; + } else { + mWriteFailed = false; + } + return (status == 1); +} + +bool PollableEvent::Clear() { + // necessary because of the "dont signal on socket thread" optimization + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + SOCKET_LOG(("PollableEvent::Clear\n")); + + if (!mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::Clear time to signal %ums", + (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear) + .ToMilliseconds())); + } + + mFirstSignalAfterClear = TimeStamp(); + mSignalTimestampAdjusted = false; + mSignaled = false; + + if (!mReadFD) { + SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n")); + return false; + } + + char buf[2048]; + int32_t status; +#ifdef XP_WIN + // On Windows we are writing to the socket for each event, to be sure that we + // do not have any deadlock read from the socket as much as we can. + while (true) { + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } else { + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; + } + } + } +#else + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + + if (status == 1) { + return true; + } + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status > 1) { + MOZ_ASSERT(false); + SOCKET_LOG(("PollableEvent::Clear Unexpected events\n")); + Clear(); + return true; + } + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; +#endif // XP_WIN +} + +void PollableEvent::MarkFirstSignalTimestamp() { + if (mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + } +} + +void PollableEvent::AdjustFirstSignalTimestamp() { + if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + mSignalTimestampAdjusted = true; + } +} + +bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) { + if (mWriteFailed) { + return false; + } + +#ifdef DEBUG + // The timeout would be just a disturbance in a debug build. + return true; +#else + if (!mSignaled || mFirstSignalAfterClear.IsNull() || + timeout == TimeDuration()) { + return true; + } + + TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear); + bool timedOut = delay > timeout; + + return !timedOut; +#endif // DEBUG +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h new file mode 100644 index 0000000000..6a17cd909d --- /dev/null +++ b/netwerk/base/PollableEvent.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PollableEvent_h__ +#define PollableEvent_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +struct PRFileDesc; + +namespace mozilla { +namespace net { + +// class must be called locked +class PollableEvent { + public: + PollableEvent(); + ~PollableEvent(); + + // Signal/Clear return false only if they fail + bool Signal(); + // This is called only when we get non-null out_flags for the socket pair + bool Clear(); + bool Valid() { return mWriteFD && mReadFD; } + + // We want to detect if writing to one of the socket pair sockets takes + // too long to be received by the other socket from the pair. + // Hence, we remember the timestamp of the earliest write by a call to + // MarkFirstSignalTimestamp() from Signal(). After waking up from poll() + // we check how long it took get the 'readable' signal on the socket pair. + void MarkFirstSignalTimestamp(); + // Called right before we enter poll() to exclude any possible delay between + // the earlist call to Signal() and entering poll() caused by processing + // of events dispatched to the socket transport thread. + void AdjustFirstSignalTimestamp(); + // This returns false on following conditions: + // - PR_Write has failed + // - no out_flags were signalled on the socket pair for too long after + // the earliest Signal() + bool IsSignallingAlive(TimeDuration const& timeout); + + PRFileDesc* PollableFD() { return mReadFD; } + + private: + PRFileDesc* mWriteFD{nullptr}; + PRFileDesc* mReadFD{nullptr}; + bool mSignaled{false}; + // true when PR_Write to the socket pair has failed (status < 1) + bool mWriteFailed{false}; + // Set true after AdjustFirstSignalTimestamp() was called + // Set false after Clear() was called + // Ensures shifting the timestamp before entering poll() only once + // between Clear()'ings. + bool mSignalTimestampAdjusted{false}; + // Timestamp of the first call to Signal() (or time we enter poll()) + // that happened after the last Clear() call + TimeStamp mFirstSignalAfterClear; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp new file mode 100644 index 0000000000..ac3176dddf --- /dev/null +++ b/netwerk/base/Predictor.cpp @@ -0,0 +1,2439 @@ +/* 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 + +#include "Predictor.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsICacheStorage.h" +#include "nsICachingChannel.h" +#include "nsICancelable.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsIDNSService.h" +#include "mozilla/dom/Document.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsIInputStream.h" +#include "nsILoadContext.h" +#include "nsILoadContextInfo.h" +#include "nsILoadGroup.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsIObserverService.h" +#include "nsISpeculativeConnect.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "mozilla/Logging.h" + +#include "mozilla/OriginAttributes.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/NeckoParent.h" + +#include "LoadContextInfo.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "mozilla/net/NeckoChild.h" + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ClearOnShutdown.h" + +#include "CacheControlParser.h" +#include "ReferrerInfo.h" + +using namespace mozilla; + +namespace mozilla { +namespace net { + +Predictor* Predictor::sSelf = nullptr; + +static LazyLogModule gPredictorLog("NetworkPredictor"); + +#define PREDICTOR_LOG(args) \ + MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args) + +#define NOW_IN_SECONDS() static_cast(PR_Now() / PR_USEC_PER_SEC) + +// All these time values are in sec +static const uint32_t ONE_DAY = 86400U; +static const uint32_t ONE_WEEK = 7U * ONE_DAY; +static const uint32_t ONE_MONTH = 30U * ONE_DAY; +static const uint32_t ONE_YEAR = 365U * ONE_DAY; + +// Version of metadata entries we expect +static const uint32_t METADATA_VERSION = 1; + +// Flags available in entries +// FLAG_PREFETCHABLE - we have determined that this item is eligible for +// prefetch +static const uint32_t FLAG_PREFETCHABLE = 1 << 0; + +// We save 12 bits in the "flags" section of our metadata for actual flags, the +// rest are to keep track of a rolling count of which loads a resource has been +// used on to determine if we can prefetch that resource or not; +static const uint8_t kRollingLoadOffset = 12; +static const int32_t kMaxPrefetchRollingLoadCount = 20; +static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1); + +// ID Extensions for cache entries +#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin" + +// Get the full origin (scheme, host, port) out of a URI (maybe should be part +// of nsIURI instead?) +static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) { + nsAutoCString s; + nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewURI(originUri, s); +} + +// All URIs we get passed *must* be http or https if they're not null. This +// helps ensure that. +static bool IsNullOrHttp(nsIURI* uri) { + if (!uri) { + return true; + } + + return uri->SchemeIs("http") || uri->SchemeIs("https"); +} + +// Listener for the speculative DNS requests we'll fire off, which just ignores +// the result (since we're just trying to warm the cache). This also exists to +// reduce round-trips to the main thread, by being something threadsafe the +// Predictor can use. + +NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener); + +NS_IMETHODIMP +Predictor::DNSListener::OnLookupComplete(nsICancelable* request, + nsIDNSRecord* rec, nsresult status) { + return NS_OK; +} + +// Class to proxy important information from the initial predictor call through +// the cache API and back into the internals of the predictor. We can't use the +// predictor itself, as it may have multiple actions in-flight, and each action +// has different parameters. +NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback); + +Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason, + nsIURI* targetURI, nsIURI* sourceURI, + nsINetworkPredictorVerifier* verifier, + Predictor* predictor) + : mFullUri(fullUri), + mPredict(predict), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mVerifier(verifier), + mStackCount(0), + mPredictor(predictor) { + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason, + nsIURI* targetURI, nsIURI* sourceURI, + nsINetworkPredictorVerifier* verifier, + Predictor* predictor, uint8_t stackCount) + : mFullUri(fullUri), + mPredict(predict), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mVerifier(verifier), + mStackCount(stackCount), + mPredictor(predictor) { + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!"); + + nsAutoCString targetURI, sourceURI; + mTargetURI->GetAsciiSpec(targetURI); + if (mSourceURI) { + mSourceURI->GetAsciiSpec(sourceURI); + } + PREDICTOR_LOG( + ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d " + "mPredictReason=%d mLearnReason=%d mTargetURI=%s " + "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32, + this, entry, mFullUri, mPredict, mPredictReason, mLearnReason, + targetURI.get(), sourceURI.get(), mStackCount, isNew, + static_cast(result))); + if (NS_FAILED(result)) { + PREDICTOR_LOG( + ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32 + "). Aborting.", + this, static_cast(result))); + return NS_OK; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime); + if (mPredict) { + bool predicted = + mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri, + mTargetURI, mVerifier, mStackCount); + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME, + mStartTime); + if (predicted) { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime); + } else { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime); + } + } else { + mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI, + mSourceURI); + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME, + mStartTime); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver, + nsISpeculativeConnectionOverrider, nsIInterfaceRequestor, + nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier) + +Predictor::Predictor() + +{ + MOZ_ASSERT(!sSelf, "multiple Predictor instances!"); + sSelf = this; +} + +Predictor::~Predictor() { + if (mInitialized) Shutdown(); + + sSelf = nullptr; +} + +// Predictor::nsIObserver + +nsresult Predictor::InstallObserver() { + MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); + + nsresult rv = NS_OK; + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +void Predictor::RemoveObserver() { + MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } +} + +NS_IMETHODIMP +Predictor::Observe(nsISupports* subject, const char* topic, + const char16_t* data_unicode) { + nsresult rv = NS_OK; + MOZ_ASSERT(NS_IsMainThread(), + "Predictor observing something off main thread!"); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + } + + return rv; +} + +// Predictor::nsISpeculativeConnectionOverrider + +NS_IMETHODIMP +Predictor::GetIgnoreIdle(bool* ignoreIdle) { + *ignoreIdle = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetParallelSpeculativeConnectLimit( + uint32_t* parallelSpeculativeConnectLimit) { + *parallelSpeculativeConnectLimit = 6; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetIsFromPredictor(bool* isFromPredictor) { + *isFromPredictor = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetAllow1918(bool* allow1918) { + *allow1918 = false; + return NS_OK; +} + +// Predictor::nsIInterfaceRequestor + +NS_IMETHODIMP +Predictor::GetInterface(const nsIID& iid, void** result) { + return QueryInterface(iid, result); +} + +// Predictor::nsICacheEntryMetaDataVisitor + +#define SEEN_META_DATA "predictor::seen" +#define RESOURCE_META_DATA "predictor::resource-count" +#define META_DATA_PREFIX "predictor::" + +static bool IsURIMetadataElement(const char* key) { + return StringBeginsWith(nsDependentCString(key), + nsLiteralCString(META_DATA_PREFIX)) && + !nsLiteralCString(SEEN_META_DATA).Equals(key) && + !nsLiteralCString(RESOURCE_META_DATA).Equals(key); +} + +nsresult Predictor::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToOperateOn.AppendElement(key); + mValuesToOperateOn.AppendElement(value); + + return NS_OK; +} + +// Predictor::nsINetworkPredictor + +nsresult Predictor::Init() { + MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild()); + + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Init called off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + + rv = InstallObserver(); + NS_ENSURE_SUCCESS(rv, rv); + + mLastStartupTime = mStartupTime = NOW_IN_SECONDS(); + + if (!mDNSListener) { + mDNSListener = new DNSListener(); + } + + mCacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup"); + NS_ENSURE_SUCCESS(rv, rv); + + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return rv; +} + +namespace { +class PredictorLearnRunnable final : public Runnable { + public: + PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& oa) + : Runnable("PredictorLearnRunnable"), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mReason(reason), + mOA(oa) { + MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI"); + } + + ~PredictorLearnRunnable() = default; + + NS_IMETHOD Run() override { + if (!gNeckoChild) { + // This may have gone away between when this runnable was dispatched and + // when it actually runs, so let's be safe here, even though we asserted + // earlier. + PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away")); + return NS_OK; + } + + PREDICTOR_LOG(("predictor::learn (async) forwarding to parent")); + gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA); + + return NS_OK; + } + + private: + nsCOMPtr mTargetURI; + nsCOMPtr mSourceURI; + PredictorLearnReason mReason; + const OriginAttributes mOA; +}; + +} // namespace + +void Predictor::Shutdown() { + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!"); + return; + } + + RemoveObserver(); + + mInitialized = false; +} + +nsresult Predictor::Create(const nsIID& aIID, void** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + RefPtr svc = new Predictor(); + if (IsNeckoChild()) { + NeckoChild::InitNeckoChild(); + + // Child threads only need to be call into the public interface methods + // so we don't bother with initialization + return svc->QueryInterface(aIID, aResult); + } + + rv = svc->Init(); + if (NS_FAILED(rv)) { + PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop")); + } + + // We treat init failure the same as the service being disabled, since this + // is all an optimization anyway. No need to freak people out. That's why we + // gladly continue on QI'ing here. + rv = svc->QueryInterface(aIID, aResult); + + return rv; +} + +NS_IMETHODIMP +Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + JS::Handle originAttributes, + nsINetworkPredictorVerifier* verifier, JSContext* aCx) { + OriginAttributes attrs; + + if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return PredictNative(targetURI, sourceURI, reason, attrs, verifier); +} + +// Called from the main thread to initiate predictive actions +NS_IMETHODIMP +Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Predict")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + // If two different threads are predicting concurently, this will be + // overwritten. Thankfully, we only use this in tests, which will + // overwrite mVerifier perhaps multiple times for each individual test; + // however, within each test, the multiple predict calls should have the + // same verifier. + if (verifier) { + PREDICTOR_LOG((" was given a verifier")); + mChildVerifier = verifier; + } + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes, + verifier); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (originAttributes.mPrivateBrowsingId > 0) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + // Nothing we can do for non-HTTP[S] schemes + PREDICTOR_LOG((" got non-http[s] URI")); + return NS_OK; + } + + // Ensure we've been given the appropriate arguments for the kind of + // prediction we're being asked to do + nsCOMPtr uriKey = targetURI; + nsCOMPtr originKey; + switch (reason) { + case nsINetworkPredictor::PREDICT_LINK: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" link invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + // Link hover is a special case where we can predict without hitting the + // db, so let's go ahead and fire off that prediction here. + PredictForLink(targetURI, sourceURI, originAttributes, verifier); + return NS_OK; + case nsINetworkPredictor::PREDICT_LOAD: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" load invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + break; + case nsINetworkPredictor::PREDICT_STARTUP: + if (targetURI || sourceURI) { + PREDICTOR_LOG((" startup invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + uriKey = mStartupURI; + originKey = mStartupURI; + break; + default: + PREDICTOR_LOG((" invalid reason")); + return NS_ERROR_INVALID_ARG; + } + + Predictor::Reason argReason{}; + argReason.mPredict = reason; + + // First we open the regular cache entry, to ensure we don't gum up the works + // waiting on the less-important predictor-only cache entry + RefPtr uriAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason, + targetURI, nullptr, verifier, this); + nsAutoCString uriKeyStr; + uriKey->GetAsciiSpec(uriKeyStr); + PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(), + reason, uriAction.get())); + + nsCOMPtr cacheDiskStorage; + + RefPtr lci = new LoadContextInfo(false, originAttributes); + + nsresult rv = mCacheStorageService->DiskCacheStorage( + lci, getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t openFlags = + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction); + + // Now we do the origin-only (and therefore predictor-only) entry + nsCOMPtr targetOrigin; + rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!originKey) { + originKey = targetOrigin; + } + + RefPtr originAction = new Predictor::Action( + Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason, + targetOrigin, nullptr, verifier, this); + nsAutoCString originKeyStr; + originKey->GetAsciiSpec(originKeyStr); + PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p", + originKeyStr.get(), reason, originAction.get())); + openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(originKey, + nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION), + openFlags, originAction); + + PREDICTOR_LOG((" predict returning")); + return NS_OK; +} + +bool Predictor::PredictInternal(PredictorPredictReason reason, + nsICacheEntry* entry, bool isNew, bool fullUri, + nsIURI* targetURI, + nsINetworkPredictorVerifier* verifier, + uint8_t stackCount) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictInternal")); + bool rv = false; + + nsCOMPtr lci; + entry->GetLoadContextInfo(getter_AddRefs(lci)); + + if (!lci) { + return rv; + } + + if (reason == nsINetworkPredictor::PREDICT_LOAD) { + MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr()); + } + + if (isNew) { + // nothing else we can do here + PREDICTOR_LOG((" new entry")); + return rv; + } + + switch (reason) { + case nsINetworkPredictor::PREDICT_LOAD: + rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier); + break; + case nsINetworkPredictor::PREDICT_STARTUP: + rv = PredictForStartup(entry, fullUri, verifier); + break; + default: + PREDICTOR_LOG((" invalid reason")); + MOZ_ASSERT(false, "Got unexpected value for prediction reason"); + } + + return rv; +} + +void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForLink")); + if (!mSpeculativeService) { + PREDICTOR_LOG((" missing speculative service")); + return; + } + + if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) { + if (sourceURI->SchemeIs("https")) { + // We don't want to predict from an HTTPS page, to avoid info leakage + PREDICTOR_LOG((" Not predicting for link hover - on an SSL page")); + return; + } + } + + nsCOMPtr principal = + BasePrincipal::CreateContentPrincipal(targetURI, originAttributes); + + mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false); + if (verifier) { + PREDICTOR_LOG((" sending verification")); + verifier->OnPredictPreconnect(targetURI); + } +} + +// This is the driver for prediction based on a new pageload. +static const uint8_t MAX_PAGELOAD_DEPTH = 10; +bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI, + uint8_t stackCount, bool fullUri, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForPageload")); + + if (stackCount > MAX_PAGELOAD_DEPTH) { + PREDICTOR_LOG((" exceeded recursion depth!")); + return false; + } + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + NS_ENSURE_SUCCESS(rv, false); + + int32_t globalDegradation = CalculateGlobalDegradation(lastLoad); + PREDICTOR_LOG((" globalDegradation = %d", globalDegradation)); + + uint32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr lci; + + rv = entry->GetLoadContextInfo(getter_AddRefs(lci)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr redirectURI; + if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation, + getter_AddRefs(redirectURI))) { + mPreconnects.AppendElement(redirectURI); + Predictor::Reason reason{}; + reason.mPredict = nsINetworkPredictor::PREDICT_LOAD; + RefPtr redirectAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason, + redirectURI, nullptr, verifier, this, stackCount + 1); + nsAutoCString redirectUriString; + redirectURI->GetAsciiSpec(redirectUriString); + + nsCOMPtr cacheDiskStorage; + + rv = mCacheStorageService->DiskCacheStorage( + lci, getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, false); + + PREDICTOR_LOG((" Predict redirect uri=%s action=%p", + redirectUriString.get(), redirectAction.get())); + uint32_t openFlags = + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags, + redirectAction); + return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier); + } + + CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, + fullUri); + + return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier); +} + +// This is the driver for predicting at browser startup time based on pages that +// have previously been loaded close to startup. +bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForStartup")); + + nsCOMPtr lci; + + nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci)); + NS_ENSURE_SUCCESS(rv, false); + + int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime); + CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount, + globalDegradation, fullUri); + return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier); +} + +// This calculates how much to degrade our confidence in our data based on +// the last time this top-level resource was loaded. This "global degradation" +// applies to *all* subresources we have associated with the top-level +// resource. This will be in addition to any reduction in confidence we have +// associated with a particular subresource. +int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) { + MOZ_ASSERT(NS_IsMainThread()); + + int32_t globalDegradation; + uint32_t delta = NOW_IN_SECONDS() - lastLoad; + if (delta < ONE_DAY) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_day(); + } else if (delta < ONE_WEEK) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_week(); + } else if (delta < ONE_MONTH) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_month(); + } else if (delta < ONE_YEAR) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_year(); + } else { + globalDegradation = StaticPrefs::network_predictor_page_degradation_max(); + } + + Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION, + globalDegradation); + return globalDegradation; +} + +// This calculates our overall confidence that a particular subresource will be +// loaded as part of a top-level load. +// @param hitCount - the number of times we have loaded this subresource as part +// of this top-level load +// @param hitsPossible - the number of times we have performed this top-level +// load +// @param lastHit - the timestamp of the last time we loaded this subresource as +// part of this top-level load +// @param lastPossible - the timestamp of the last time we performed this +// top-level load +// @param globalDegradation - the degradation for this top-level load as +// determined by CalculateGlobalDegradation +int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation) { + MOZ_ASSERT(NS_IsMainThread()); + + Telemetry::AutoCounter + predictionsCalculated; + ++predictionsCalculated; + + if (!hitsPossible) { + return 0; + } + + int32_t baseConfidence = (hitCount * 100) / hitsPossible; + int32_t maxConfidence = 100; + int32_t confidenceDegradation = 0; + + if (lastHit < lastPossible) { + // We didn't load this subresource the last time this top-level load was + // performed, so let's not bother preconnecting (at the very least). + maxConfidence = + StaticPrefs::network_predictor_preconnect_min_confidence() - 1; + + // Now calculate how much we want to degrade our confidence based on how + // long it's been between the last time we did this top-level load and the + // last time this top-level load included this subresource. + PRTime delta = lastPossible - lastHit; + if (delta == 0) { + confidenceDegradation = 0; + } else if (delta < ONE_DAY) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_day(); + } else if (delta < ONE_WEEK) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_week(); + } else if (delta < ONE_MONTH) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_month(); + } else if (delta < ONE_YEAR) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_year(); + } else { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_max(); + maxConfidence = 0; + } + } + + // Calculate our confidence and clamp it to between 0 and maxConfidence + // (<= 100) + int32_t confidence = + baseConfidence - confidenceDegradation - globalDegradation; + confidence = std::max(confidence, 0); + confidence = std::min(confidence, maxConfidence); + + Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence); + Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION, + confidenceDegradation); + Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence); + return confidence; +} + +static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit, + const uint32_t flags, nsCString& newValue) { + newValue.Truncate(); + newValue.AppendInt(METADATA_VERSION); + newValue.Append(','); + newValue.AppendInt(hitCount); + newValue.Append(','); + newValue.AppendInt(lastHit); + newValue.Append(','); + newValue.AppendInt(flags); +} + +// On every page load, the rolling window gets shifted by one bit, leaving the +// lowest bit at 0, to indicate that the subresource in question has not been +// seen on the most recent page load. If, at some point later during the page +// load, the subresource is seen again, we will then set the lowest bit to 1. +// This is how we keep track of how many of the last n pageloads (for n <= 20) a +// particular subresource has been seen. The rolling window is kept in the upper +// 20 bits of the flags element of the metadata. This saves 12 bits for regular +// old flags. +void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry, + const uint32_t flags, const char* key, + const uint32_t hitCount, + const uint32_t lastHit) { + // Extract just the rolling load count from the flags, shift it to clear the + // lowest bit, and put the new value with the existing flags. + uint32_t rollingLoadCount = flags & ~kFlagsMask; + rollingLoadCount <<= 1; + uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount; + + // Finally, update the metadata on the cache entry. + nsAutoCString newValue; + MakeMetadataEntry(hitCount, lastHit, newFlags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); +} + +uint32_t Predictor::ClampedPrefetchRollingLoadCount() { + int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count(); + if (n < 0) { + return 0; + } + if (n > kMaxPrefetchRollingLoadCount) { + return kMaxPrefetchRollingLoadCount; + } + return n; +} + +void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri) { + MOZ_ASSERT(NS_IsMainThread()); + + // Since the visitor gets called under a cache lock, all we do there is get + // copies of the keys/values we care about, and then do the real work here + entry->VisitMetaData(this); + nsTArray keysToOperateOn = std::move(mKeysToOperateOn), + valuesToOperateOn = std::move(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char* key = keysToOperateOn[i].BeginReading(); + const char* value = valuesToOperateOn[i].BeginReading(); + + nsCString uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + + int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit, + lastLoad, globalDegradation); + if (fullUri) { + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, + value, confidence)); + PrefetchIgnoreReason reason = PREFETCH_OK; + if (!fullUri) { + // Not full URI - don't prefetch! No sense in it! + PREDICTOR_LOG((" forcing non-cacheability - not full URI")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = NOT_FULL_URI; + } + flags &= ~FLAG_PREFETCHABLE; + } else if (!referrer) { + // No referrer means we can't prefetch, so pretend it's non-cacheable, + // no matter what. + PREDICTOR_LOG((" forcing non-cacheability - no referrer")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = NO_REFERRER; + } + flags &= ~FLAG_PREFETCHABLE; + } else { + uint32_t expectedRollingLoadCount = + (1 << ClampedPrefetchRollingLoadCount()) - 1; + expectedRollingLoadCount <<= kRollingLoadOffset; + if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) { + PREDICTOR_LOG((" forcing non-cacheability - missed a load")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = MISSED_A_LOAD; + } + flags &= ~FLAG_PREFETCHABLE; + } + } + + PREDICTOR_LOG((" setting up prediction")); + SetupPrediction(confidence, flags, uri, reason); + } +} + +// (Maybe) adds a predictive action to the prediction runner, based on our +// calculated confidence for the subresource in question. +void Predictor::SetupPrediction(int32_t confidence, uint32_t flags, + const nsCString& uri, + PrefetchIgnoreReason earlyReason) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = NS_OK; + PREDICTOR_LOG( + ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d " + "preconnect-min-confidence=%d preresolve-min-confidence=%d " + "flags=%d confidence=%d uri=%s", + StaticPrefs::network_predictor_enable_prefetch(), + StaticPrefs::network_predictor_prefetch_min_confidence(), + StaticPrefs::network_predictor_preconnect_min_confidence(), + StaticPrefs::network_predictor_preresolve_min_confidence(), flags, + confidence, uri.get())); + + bool prefetchOk = !!(flags & FLAG_PREFETCHABLE); + PrefetchIgnoreReason reason = earlyReason; + if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) { + prefetchOk = false; + reason = PREFETCH_DISABLED; + } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() && + confidence < + StaticPrefs::network_predictor_prefetch_min_confidence()) { + prefetchOk = false; + if (!ClampedPrefetchRollingLoadCount()) { + reason = PREFETCH_DISABLED_VIA_COUNT; + } else { + reason = CONFIDENCE_TOO_LOW; + } + } + + // prefetchOk == false and reason == PREFETCH_OK indicates that the reason + // we aren't prefetching this item is because it was marked un-prefetchable in + // our metadata. We already have separate telemetry on that decision, so we + // aren't going to accumulate more here. Right now we only care about why + // something we had marked prefetchable isn't being prefetched. + if (!prefetchOk && reason != PREFETCH_OK) { + Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason); + } + + if (prefetchOk) { + nsCOMPtr prefetchURI; + rv = NS_NewURI(getter_AddRefs(prefetchURI), uri); + if (NS_SUCCEEDED(rv)) { + mPrefetches.AppendElement(prefetchURI); + } + } else if (confidence >= + StaticPrefs::network_predictor_preconnect_min_confidence()) { + nsCOMPtr preconnectURI; + rv = NS_NewURI(getter_AddRefs(preconnectURI), uri); + if (NS_SUCCEEDED(rv)) { + mPreconnects.AppendElement(preconnectURI); + } + } else if (confidence >= + StaticPrefs::network_predictor_preresolve_min_confidence()) { + nsCOMPtr preresolveURI; + rv = NS_NewURI(getter_AddRefs(preresolveURI), uri); + if (NS_SUCCEEDED(rv)) { + mPreresolves.AppendElement(preresolveURI); + } + } + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" NS_NewURI returned 0x%" PRIx32, static_cast(rv))); + } +} + +nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + nsAutoCString strUri, strReferrer; + uri->GetAsciiSpec(strUri); + referrer->GetAsciiSpec(strReferrer); + PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p", + strUri.get(), strReferrer.get(), verifier)); + nsCOMPtr channel; + nsresult rv = NS_NewChannel( + getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */ + nullptr, /* aPerformanceStorage */ + nullptr, /* aLoadGroup */ + nullptr, /* aCallbacks */ + nsIRequest::LOAD_BACKGROUND); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" NS_NewChannel failed rv=0x%" PRIX32, static_cast(rv))); + return rv; + } + + nsCOMPtr loadInfo = channel->LoadInfo(); + rv = loadInfo->SetOriginAttributes(originAttributes); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" Set originAttributes into loadInfo failed rv=0x%" PRIX32, + static_cast(rv))); + return rv; + } + + nsCOMPtr httpChannel; + httpChannel = do_QueryInterface(channel); + if (!httpChannel) { + PREDICTOR_LOG((" Could not get HTTP Channel from new channel!")); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr referrerInfo = new dom::ReferrerInfo(referrer); + rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + // XXX - set a header here to indicate this is a prefetch? + + nsCOMPtr listener = + new PrefetchListener(verifier, uri, this); + PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(), + channel.get())); + rv = channel->AsyncOpen(listener); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" AsyncOpen failed rv=0x%" PRIX32, static_cast(rv))); + } + + return rv; +} + +// Runs predictions that have been set up. +bool Predictor::RunPredictions(nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); + + PREDICTOR_LOG(("Predictor::RunPredictions")); + + bool predicted = false; + uint32_t len, i; + + nsTArray> prefetches = std::move(mPrefetches), + preconnects = std::move(mPreconnects), + preresolves = std::move(mPreresolves); + + Telemetry::AutoCounter + totalPredictions; + Telemetry::AutoCounter totalPrefetches; + Telemetry::AutoCounter + totalPreconnects; + Telemetry::AutoCounter + totalPreresolves; + + len = prefetches.Length(); + for (i = 0; i < len; ++i) { + PREDICTOR_LOG((" doing prefetch")); + nsCOMPtr uri = prefetches[i]; + if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) { + ++totalPredictions; + ++totalPrefetches; + predicted = true; + } + } + + len = preconnects.Length(); + for (i = 0; i < len; ++i) { + PREDICTOR_LOG((" doing preconnect")); + nsCOMPtr uri = preconnects[i]; + ++totalPredictions; + ++totalPreconnects; + nsCOMPtr principal = + BasePrincipal::CreateContentPrincipal(uri, originAttributes); + mSpeculativeService->SpeculativeConnect(uri, principal, this, false); + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preconnect verification")); + verifier->OnPredictPreconnect(uri); + } + } + + len = preresolves.Length(); + for (i = 0; i < len; ++i) { + nsCOMPtr uri = preresolves[i]; + ++totalPredictions; + ++totalPreresolves; + nsAutoCString hostname; + uri->GetAsciiHost(hostname); + PREDICTOR_LOG((" doing preresolve %s", hostname.get())); + nsCOMPtr tmpCancelable; + mDnsService->AsyncResolveNative( + hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + nullptr, mDNSListener, nullptr, originAttributes, + getter_AddRefs(tmpCancelable)); + + // Fetch HTTPS RR if needed. + if (StaticPrefs::network_dns_upgrade_with_https_rr() || + StaticPrefs::network_dns_use_https_rr_as_altsvc()) { + mDnsService->AsyncResolveNative( + hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + nullptr, mDNSListener, nullptr, originAttributes, + getter_AddRefs(tmpCancelable)); + } + + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preresolve verification")); + verifier->OnPredictDNS(uri); + } + } + + return predicted; +} + +// Find out if a top-level page is likely to redirect. +bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI** redirectURI) { + // TODO - not doing redirects for first go around + MOZ_ASSERT(NS_IsMainThread()); + + return false; +} + +NS_IMETHODIMP +Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + JS::Handle originAttributes, JSContext* aCx) { + OriginAttributes attrs; + + if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return LearnNative(targetURI, sourceURI, reason, attrs); +} + +// Called from the main thread to update the database +NS_IMETHODIMP +Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Learn")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + + RefPtr runnable = new PredictorLearnRunnable( + targetURI, sourceURI, reason, originAttributes); + SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()); + + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (originAttributes.mPrivateBrowsingId > 0) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + PREDICTOR_LOG((" got non-HTTP[S] URI")); + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr targetOrigin; + nsCOMPtr sourceOrigin; + nsCOMPtr uriKey; + nsCOMPtr originKey; + nsresult rv; + + switch (reason) { + case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" load toplevel invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = targetURI; + originKey = targetOrigin; + break; + case nsINetworkPredictor::LEARN_STARTUP: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" startup invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = mStartupURI; + originKey = mStartupURI; + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" redirect/subresource invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = sourceURI; + originKey = sourceOrigin; + break; + default: + PREDICTOR_LOG((" invalid reason")); + return NS_ERROR_INVALID_ARG; + } + + Telemetry::AutoCounter 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 uriAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason, + targetURI, sourceURI, nullptr, this); + nsAutoCString uriKeyStr, targetUriStr, sourceUriStr; + uriKey->GetAsciiSpec(uriKeyStr); + targetURI->GetAsciiSpec(targetUriStr); + if (sourceURI) { + sourceURI->GetAsciiSpec(sourceUriStr); + } + PREDICTOR_LOG( + (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d " + "action=%p", + uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason, + uriAction.get())); + + nsCOMPtr cacheDiskStorage; + + RefPtr lci = new LoadContextInfo(false, originAttributes); + + rv = mCacheStorageService->DiskCacheStorage(lci, + getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + // For learning full URI things, we *always* open readonly and secretly, as we + // rely on actual pageloads to update the entry's metadata for us. + uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // Learning for toplevel we want to open the full uri entry priority, since + // it's likely this entry will be used soon anyway, and we want this to be + // opened ASAP. + uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY; + } + cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction); + + // Now we open the origin-only (and therefore predictor-only) entry + RefPtr originAction = new Predictor::Action( + Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason, + targetOrigin, sourceOrigin, nullptr, this); + nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr; + originKey->GetAsciiSpec(originKeyStr); + targetOrigin->GetAsciiSpec(targetOriginStr); + if (sourceOrigin) { + sourceOrigin->GetAsciiSpec(sourceOriginStr); + } + PREDICTOR_LOG( + (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d " + "action=%p", + originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason, + originAction.get())); + uint32_t originOpenFlags; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // This is the only case when we want to update the 'last used' metadata on + // the cache entry we're getting. This only applies to predictor-specific + // entries. + originOpenFlags = + nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED; + } else { + originOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + } + cacheDiskStorage->AsyncOpenURI(originKey, + nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION), + originOpenFlags, originAction); + + PREDICTOR_LOG(("Predictor::Learn returning")); + return NS_OK; +} + +void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsIURI* sourceURI) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnInternal")); + + nsCString junk; + if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL && + NS_FAILED( + entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) { + // This is an origin-only entry that we haven't seen before. Let's mark it + // as seen. + PREDICTOR_LOG((" marking new origin entry as seen")); + nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1"); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to mark origin entry seen")); + return; + } + + // Need to ensure someone else can get to the entry if necessary + entry->MetaDataReady(); + return; + } + + switch (reason) { + case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL: + // This case only exists to be used during tests - code outside the + // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL. + // The predictor xpcshell test needs this branch, however, because we + // have no real page loads in xpcshell, and this is how we fake it up + // so that all the work that normally happens behind the scenes in a + // page load can be done for testing purposes. + if (fullUri && StaticPrefs::network_predictor_doing_tests()) { + PREDICTOR_LOG( + (" WARNING - updating rolling load count. " + "If you see this outside tests, you did it wrong")); + + // Since the visitor gets called under a cache lock, all we do there is + // get copies of the keys/values we care about, and then do the real + // work here + entry->VisitMetaData(this); + nsTArray keysToOperateOn = std::move(mKeysToOperateOn), + valuesToOperateOn = std::move(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char* key = keysToOperateOn[i].BeginReading(); + const char* value = valuesToOperateOn[i].BeginReading(); + + nsCString uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + } else { + PREDICTOR_LOG((" nothing to do for toplevel")); + } + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + if (fullUri) { + LearnForRedirect(entry, targetURI); + } + break; + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + LearnForSubresource(entry, targetURI); + break; + case nsINetworkPredictor::LEARN_STARTUP: + LearnForStartup(entry, targetURI); + break; + default: + PREDICTOR_LOG((" unexpected reason value")); + MOZ_ASSERT(false, "Got unexpected value for learn reason!"); + } +} + +NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor) + +NS_IMETHODIMP +Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(key)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + nsCString uri; + uint32_t hitCount, lastHit, flags; + bool ok = + mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags); + + if (!ok) { + // Couldn't parse this one, just get rid of it + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + uint32_t uriLength = uri.Length(); + if (uriLength > StaticPrefs::network_predictor_max_uri_length()) { + // Default to getting rid of URIs that are too long and were put in before + // we had our limit on URI length, in order to free up some space. + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + if (!mLRUKeyToDelete || lastHit < mLRUStamp) { + mLRUKeyToDelete = key; + mLRUStamp = lastHit; + } + + return NS_OK; +} + +void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLRUKeyToDelete) { + entry->SetMetaDataElement(mLRUKeyToDelete, nullptr); + } + + for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) { + entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr); + } +} + +// Called when a subresource has been hit from a top-level load. Uses the two +// helper functions above to update the database appropriately. +void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnForSubresource")); + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + NS_ENSURE_SUCCESS_VOID(rv); + + uint32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString key; + key.AssignLiteral(META_DATA_PREFIX); + nsCString uri; + targetURI->GetAsciiSpec(uri); + key.Append(uri); + if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) { + // We do this to conserve space/prevent OOMs + PREDICTOR_LOG((" uri too long!")); + entry->SetMetaDataElement(key.BeginReading(), nullptr); + return; + } + + nsCString value; + rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value)); + + uint32_t hitCount, lastHit, flags; + bool isNewResource = + (NS_FAILED(rv) || + !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri, + hitCount, lastHit, flags)); + + int32_t resourceCount = 0; + if (isNewResource) { + // This is a new addition + PREDICTOR_LOG((" new resource")); + nsCString s; + rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s)); + if (NS_SUCCEEDED(rv)) { + resourceCount = atoi(s.BeginReading()); + } + if (resourceCount >= + StaticPrefs::network_predictor_max_resources_per_entry()) { + RefPtr cleaner = + new Predictor::SpaceCleaner(this); + entry->VisitMetaData(cleaner); + cleaner->Finalize(entry); + } else { + ++resourceCount; + } + nsAutoCString count; + count.AppendInt(resourceCount); + rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to update resource count")); + return; + } + hitCount = 1; + flags = 0; + } else { + PREDICTOR_LOG((" existing resource")); + hitCount = std::min(hitCount + 1, loadCount); + } + + // Update the rolling load count to mark this sub-resource as seen on the + // most-recent pageload so it can be eligible for prefetch (assuming all + // the other stars align). + flags |= (1 << kRollingLoadOffset); + + nsCString newValue; + MakeMetadataEntry(hitCount, lastLoad, flags, newValue); + rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading()); + PREDICTOR_LOG( + (" SetMetaDataElement -> 0x%08" PRIX32, static_cast(rv))); + if (NS_FAILED(rv) && isNewResource) { + // Roll back the increment to the resource count we made above. + PREDICTOR_LOG((" rolling back resource count update")); + --resourceCount; + if (resourceCount == 0) { + entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr); + } else { + nsAutoCString count; + count.AppendInt(resourceCount); + entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + } + } +} + +// This is called when a top-level loaded ended up redirecting to a different +// URI so we can keep track of that fact. +void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing redirects for first go around + PREDICTOR_LOG(("Predictor::LearnForRedirect")); +} + +// This will add a page to our list of startup pages if it's being loaded +// before our startup window has expired. +void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing startup for first go around + PREDICTOR_LOG(("Predictor::MaybeLearnForStartup")); +} + +// Add information about a top-level load to our list of startup pages +void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + // These actually do the same set of work, just on different entries, so we + // can pass through to get the real work done here + PREDICTOR_LOG(("Predictor::LearnForStartup")); + LearnForSubresource(entry, targetURI); +} + +bool Predictor::ParseMetaDataEntry(const char* key, const char* value, + nsCString& uri, uint32_t& hitCount, + uint32_t& lastHit, uint32_t& flags) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG( + ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value)); + + const char* comma = strchr(value, ','); + if (!comma) { + PREDICTOR_LOG((" could not find first comma")); + return false; + } + + uint32_t version = static_cast(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(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(atoi(value)); + PREDICTOR_LOG((" lastHit -> %u", lastHit)); + + value = comma + 1; + flags = static_cast(atoi(value)); + PREDICTOR_LOG((" flags -> %u", flags)); + + if (key) { + const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1); + uri.AssignASCII(uriStart); + PREDICTOR_LOG((" uri -> %s", uriStart)); + } else { + uri.Truncate(); + } + + return true; +} + +NS_IMETHODIMP +Predictor::Reset() { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Reset")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredReset(); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + RefPtr reset = new Predictor::Resetter(this); + PREDICTOR_LOG((" created a resetter")); + mCacheStorageService->AsyncVisitAllStorages(reset, true); + PREDICTOR_LOG((" Cache async launched, returning now")); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor); + +Predictor::Resetter::Resetter(Predictor* predictor) + : mEntriesToVisit(0), mPredictor(predictor) {} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_FAILED(result)) { + // This can happen when we've tried to open an entry that doesn't exist for + // some non-reset operation, and then get reset shortly thereafter (as + // happens in some of our tests). + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + return NS_OK; + } + + entry->VisitMetaData(this); + nsTArray keysToDelete = std::move(mKeysToDelete); + + for (size_t i = 0; i < keysToDelete.Length(); ++i) { + const char* key = keysToDelete[i].BeginReading(); + entry->SetMetaDataElement(key, nullptr); + } + + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!StringBeginsWith(nsDependentCString(asciiKey), + nsLiteralCString(META_DATA_PREFIX))) { + // Not a metadata entry we care about, carry on + return NS_OK; + } + + nsCString key; + key.AssignASCII(asciiKey); + mKeysToDelete.AppendElement(key); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, + uint64_t consumption, uint64_t capacity, + nsIFile* diskDirectory) { + MOZ_ASSERT(NS_IsMainThread()); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance, + int64_t dataSize, int64_t altDataSize, + uint32_t fetchCount, + uint32_t lastModifiedTime, + uint32_t expirationTime, bool aPinned, + nsILoadContextInfo* aInfo) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + // The predictor will only ever touch entries with no idEnhance ("") or an + // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that + // don't match that to avoid doing extra work. + if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) { + // This is an entry we own, so we can just doom it entirely + nsCOMPtr cacheDiskStorage; + + rv = mPredictor->mCacheStorageService->DiskCacheStorage( + aInfo, getter_AddRefs(cacheDiskStorage)); + + NS_ENSURE_SUCCESS(rv, rv); + cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr); + } else if (idEnhance.IsEmpty()) { + // This is an entry we don't own, so we have to be a little more careful and + // just get rid of our own metadata entries. Append it to an array of things + // to operate on and then do the operations later so we don't end up calling + // Complete() multiple times/too soon. + ++mEntriesToVisit; + mURIsToVisit.AppendElement(uri); + mInfosToVisit.AppendElement(aInfo); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryVisitCompleted() { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + nsTArray> urisToVisit = std::move(mURIsToVisit); + + MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length()); + + nsTArray> infosToVisit = + std::move(mInfosToVisit); + + MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length()); + + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + + uint32_t entriesToVisit = urisToVisit.Length(); + for (uint32_t i = 0; i < entriesToVisit; ++i) { + nsCString u; + nsCOMPtr cacheDiskStorage; + + rv = mPredictor->mCacheStorageService->DiskCacheStorage( + infosToVisit[i], getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + urisToVisit[i]->GetAsciiSpec(u); + rv = cacheDiskStorage->AsyncOpenURI( + urisToVisit[i], ""_ns, + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED, + this); + if (NS_FAILED(rv)) { + mEntriesToVisit--; + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + } + } + + return NS_OK; +} + +void Predictor::Resetter::Complete() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!")); + return; + } + + obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr); +} + +// Helper functions to make using the predictor easier from native code + +static StaticRefPtr sPredictor; + +static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sPredictor) { + nsresult rv; + nsCOMPtr predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + sPredictor = predictor; + ClearOnShutdown(&sPredictor); + } + + nsCOMPtr predictor = sPredictor.get(); + predictor.forget(aPredictor); + return NS_OK; +} + +nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->PredictNative(targetURI, sourceURI, reason, + originAttributes, verifier); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, nsILoadGroup* loadGroup) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr loadContext; + OriginAttributes originAttributes; + + if (loadGroup) { + nsCOMPtr callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + loadContext = do_GetInterface(callbacks); + + if (loadContext) { + loadContext->GetOriginAttributes(originAttributes); + } + } + } + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, dom::Document* document) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes originAttributes; + + if (document) { + nsCOMPtr docPrincipal = document->NodePrincipal(); + + if (docPrincipal) { + originAttributes = docPrincipal->OriginAttributesRef(); + } + } + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr 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 predictor; + rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->LearnNative(targetURI, sourceURI, + nsINetworkPredictor::LEARN_LOAD_REDIRECT, + originAttributes); +} + +// nsINetworkPredictorVerifier + +/** + * Call through to the child's verifier (only during tests) + */ +NS_IMETHODIMP +Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPrefetch(aURI, httpStatus); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictPreconnect(nsIURI* aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPreconnect(aURI); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPreconnect(aURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictDNS(nsIURI* aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictDNS(aURI); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictDNS(aURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +// Predictor::PrefetchListener +// nsISupports +NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener, + nsIRequestObserver) + +// nsIRequestObserver +NS_IMETHODIMP +Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) { + mStartTime = TimeStamp::Now(); + return NS_OK; +} + +NS_IMETHODIMP +Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this, + static_cast(aStatusCode))); + NS_ENSURE_ARG(aRequest); + if (NS_FAILED(aStatusCode)) { + return aStatusCode; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, + mStartTime); + + nsCOMPtr httpChannel = do_QueryInterface(aRequest); + if (!httpChannel) { + PREDICTOR_LOG((" Could not get HTTP Channel!")); + return NS_ERROR_UNEXPECTED; + } + nsCOMPtr cachingChannel = do_QueryInterface(httpChannel); + if (!cachingChannel) { + PREDICTOR_LOG((" Could not get caching channel!")); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + uint32_t httpStatus; + rv = httpChannel->GetResponseStatus(&httpStatus); + if (NS_SUCCEEDED(rv) && httpStatus == 200) { + rv = cachingChannel->ForceCacheEntryValidFor( + StaticPrefs::network_predictor_prefetch_force_valid_for()); + PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32, + StaticPrefs::network_predictor_prefetch_force_valid_for(), + static_cast(rv))); + } else { + rv = cachingChannel->ForceCacheEntryValidFor(0); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200); + PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32, + static_cast(rv))); + } + + nsAutoCString reqName; + rv = aRequest->GetName(reqName); + if (NS_FAILED(rv)) { + reqName.AssignLiteral(""); + } + + PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus)); + + if (mVerifier) { + mVerifier->OnPredictPrefetch(mURI, httpStatus); + } + + return rv; +} + +// nsIStreamListener +NS_IMETHODIMP +Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + const uint32_t aCount) { + uint32_t result; + return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, + &result); +} + +// Miscellaneous Predictor + +void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, + nsHttpRequestHead& requestHead, + nsHttpResponseHead* responseHead, + nsILoadContextInfo* lci, bool isTracking) { + MOZ_ASSERT(NS_IsMainThread()); + + if (lci && lci->IsPrivate()) { + PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring")); + return; + } + + if (!sourceURI || !targetURI) { + PREDICTOR_LOG( + ("Predictor::UpdateCacheability missing source or target uri")); + return; + } + + if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) { + PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri")); + return; + } + + RefPtr self = sSelf; + if (self) { + nsAutoCString method; + requestHead.Method(method); + + nsAutoCString vary; + Unused << responseHead->GetHeader(nsHttp::Vary, vary); + + nsAutoCString cacheControlHeader; + Unused << responseHead->GetHeader(nsHttp::Cache_Control, + cacheControlHeader); + CacheControlParser cacheControl(cacheControlHeader); + + self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method, + *lci->OriginAttributesPtr(), isTracking, + !vary.IsEmpty(), cacheControl.NoStore()); + } +} + +void Predictor::UpdateCacheabilityInternal( + nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus, + const nsCString& method, const OriginAttributes& originAttributes, + bool isTracking, bool couldVary, bool isNoStore) { + PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus)); + + nsresult rv; + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return; + } + + nsCOMPtr cacheDiskStorage; + + RefPtr lci = new LoadContextInfo(false, originAttributes); + + rv = mCacheStorageService->DiskCacheStorage(lci, + getter_AddRefs(cacheDiskStorage)); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" cannot get disk cache storage")); + return; + } + + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + RefPtr action = + new Predictor::CacheabilityAction(targetURI, httpStatus, method, + isTracking, couldVary, isNoStore, this); + nsAutoCString uri; + targetURI->GetAsciiSpec(uri); + PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get())); + cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action); +} + +NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor); + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry, + uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +namespace { +enum PrefetchDecisionReason { + PREFETCHABLE, + STATUS_NOT_200, + METHOD_NOT_GET, + URL_HAS_QUERY_STRING, + RESOURCE_IS_TRACKING, + RESOURCE_COULD_VARY, + RESOURCE_IS_NO_STORE +}; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry, + bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread()); + // This is being opened read-only, so isNew should always be false + MOZ_ASSERT(!isNew); + + PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this)); + if (NS_FAILED(result)) { + // Nothing to do + PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d", + static_cast(result), isNew)); + return NS_OK; + } + + nsCString strTargetURI; + nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" GetAsciiSpec returned %" PRIx32, static_cast(rv))); + return NS_OK; + } + + rv = entry->VisitMetaData(this); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" VisitMetaData returned %" PRIx32, static_cast(rv))); + return NS_OK; + } + + nsTArray keysToCheck = std::move(mKeysToCheck), + valuesToCheck = std::move(mValuesToCheck); + + bool hasQueryString = false; + nsAutoCString query; + if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) { + hasQueryString = true; + } + + MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length()); + for (size_t i = 0; i < keysToCheck.Length(); ++i) { + const char* key = keysToCheck[i].BeginReading(); + const char* value = valuesToCheck[i].BeginReading(); + nsCString uri; + uint32_t hitCount, lastHit, flags; + + if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, + flags)) { + PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value)); + continue; + } + + if (strTargetURI.Equals(uri)) { + bool prefetchable = true; + PrefetchDecisionReason reason = PREFETCHABLE; + + if (mHttpStatus != 200) { + prefetchable = false; + reason = STATUS_NOT_200; + } else if (!mMethod.EqualsLiteral("GET")) { + prefetchable = false; + reason = METHOD_NOT_GET; + } else if (hasQueryString) { + prefetchable = false; + reason = URL_HAS_QUERY_STRING; + } else if (mIsTracking) { + prefetchable = false; + reason = RESOURCE_IS_TRACKING; + } else if (mCouldVary) { + prefetchable = false; + reason = RESOURCE_COULD_VARY; + } else if (mIsNoStore) { + // We don't set prefetchable = false yet, because we just want to know + // what kind of effect this would have on prefetching. + reason = RESOURCE_IS_NO_STORE; + } + + Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON, + reason); + + if (prefetchable) { + PREDICTOR_LOG((" marking %s cacheable", key)); + flags |= FLAG_PREFETCHABLE; + } else { + PREDICTOR_LOG((" marking %s uncacheable", key)); + flags &= ~FLAG_PREFETCHABLE; + } + nsCString newValue; + MakeMetadataEntry(hitCount, lastHit, flags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToCheck.AppendElement(key); + mValuesToCheck.AppendElement(value); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h new file mode 100644 index 0000000000..a16e09adad --- /dev/null +++ b/netwerk/base/Predictor.h @@ -0,0 +1,458 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Predictor_h +#define mozilla_net_Predictor_h + +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" + +#include "nsCOMPtr.h" +#include "nsICacheEntry.h" +#include "nsICacheEntryOpenCallback.h" +#include "nsICacheStorageService.h" +#include "nsICacheStorageVisitor.h" +#include "nsIDNSListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsISpeculativeConnect.h" +#include "nsIStreamListener.h" +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "mozilla/TimeStamp.h" + +class nsICacheStorage; +class nsIDNSService; +class nsIIOService; +class nsILoadContextInfo; +class nsITimer; + +namespace mozilla { +namespace net { + +class nsHttpRequestHead; +class nsHttpResponseHead; + +class Predictor final : public nsINetworkPredictor, + public nsIObserver, + public nsISpeculativeConnectionOverrider, + public nsIInterfaceRequestor, + public nsICacheEntryMetaDataVisitor, + public nsINetworkPredictorVerifier { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKPREDICTOR + NS_DECL_NSIOBSERVER + NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSINETWORKPREDICTORVERIFIER + + Predictor(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(const nsIID& iid, void** result); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + static void UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, + nsHttpRequestHead& requestHead, + nsHttpResponseHead* responseHead, + nsILoadContextInfo* lci, bool isTracking); + + private: + virtual ~Predictor(); + + // Stores callbacks for a child process predictor (for test purposes) + nsCOMPtr mChildVerifier; + + union Reason { + PredictorLearnReason mLearn; + PredictorPredictReason mPredict; + }; + + class DNSListener : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + DNSListener() = default; + + private: + virtual ~DNSListener() = default; + }; + + class Action : public nsICacheEntryOpenCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + + Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI, + nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier, + Predictor* predictor); + Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI, + nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier, + Predictor* predictor, uint8_t stackCount); + + static const bool IS_FULL_URI = true; + static const bool IS_ORIGIN = false; + + static const bool DO_PREDICT = true; + static const bool DO_LEARN = false; + + private: + virtual ~Action() = default; + + bool mFullUri : 1; + bool mPredict : 1; + union { + PredictorPredictReason mPredictReason; + PredictorLearnReason mLearnReason; + }; + nsCOMPtr mTargetURI; + nsCOMPtr mSourceURI; + nsCOMPtr mVerifier; + TimeStamp mStartTime; + uint8_t mStackCount; + RefPtr mPredictor; + }; + + class CacheabilityAction : public nsICacheEntryOpenCallback, + public nsICacheEntryMetaDataVisitor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSICACHEENTRYMETADATAVISITOR + + CacheabilityAction(nsIURI* targetURI, uint32_t httpStatus, + const nsCString& method, bool isTracking, bool couldVary, + bool isNoStore, Predictor* predictor) + : mTargetURI(targetURI), + mHttpStatus(httpStatus), + mMethod(method), + mIsTracking(isTracking), + mCouldVary(couldVary), + mIsNoStore(isNoStore), + mPredictor(predictor) {} + + private: + virtual ~CacheabilityAction() = default; + + nsCOMPtr mTargetURI; + uint32_t mHttpStatus; + nsCString mMethod; + bool mIsTracking; + bool mCouldVary; + bool mIsNoStore; + RefPtr mPredictor; + nsTArray mKeysToCheck; + nsTArray mValuesToCheck; + }; + + class Resetter : public nsICacheEntryOpenCallback, + public nsICacheEntryMetaDataVisitor, + public nsICacheStorageVisitor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSICACHESTORAGEVISITOR + + explicit Resetter(Predictor* predictor); + + private: + virtual ~Resetter() = default; + + void Complete(); + + uint32_t mEntriesToVisit; + nsTArray mKeysToDelete; + RefPtr mPredictor; + nsTArray> mURIsToVisit; + nsTArray> mInfosToVisit; + }; + + class SpaceCleaner : public nsICacheEntryMetaDataVisitor { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYMETADATAVISITOR + + explicit SpaceCleaner(Predictor* predictor) + : mLRUStamp(0), mLRUKeyToDelete(nullptr), mPredictor(predictor) {} + + void Finalize(nsICacheEntry* entry); + + private: + virtual ~SpaceCleaner() = default; + uint32_t mLRUStamp; + const char* mLRUKeyToDelete; + nsTArray mLongKeysToDelete; + RefPtr mPredictor; + }; + + class PrefetchListener : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + PrefetchListener(nsINetworkPredictorVerifier* verifier, nsIURI* uri, + Predictor* predictor) + : mVerifier(verifier), mURI(uri), mPredictor(predictor) {} + + private: + virtual ~PrefetchListener() = default; + + nsCOMPtr mVerifier; + nsCOMPtr mURI; + RefPtr mPredictor; + TimeStamp mStartTime; + }; + + // Observer-related stuff + nsresult InstallObserver(); + void RemoveObserver(); + + // Service startup utilities + void MaybeCleanupOldDBFiles(); + + // The guts of prediction + + // This is the top-level driver for doing any prediction that needs + // information from the cache. Returns true if any predictions were queued up + // * reason - What kind of prediction this is/why this prediction is + // happening (pageload, startup) + // * entry - the cache entry with the information we need + // * isNew - whether or not the cache entry is brand new and empty + // * fullUri - whether we are doing predictions based on a full page URI, or + // just the origin of the page + // * targetURI - the URI that we are predicting based upon - IOW, the URI + // that is being loaded or being redirected to + // * verifier - used for testing to verify the expected predictions happen + // * stackCount - used to ensure we don't recurse too far trying to find the + // final redirection in a redirect chain + bool PredictInternal(PredictorPredictReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsINetworkPredictorVerifier* verifier, + uint8_t stackCount); + + // Used when predicting because the user's mouse hovered over a link + // * targetURI - the URI target of the link + // * sourceURI - the URI of the page on which the link appears + // * originAttributes - the originAttributes for this prediction + // * verifier - used for testing to verify the expected predictions happen + void PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used when predicting because a page is being loaded (which may include + // being the target of a redirect). All arguments are the same as for + // PredictInternal. Returns true if any predictions were queued up. + bool PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI, + uint8_t stackCount, bool fullUri, + nsINetworkPredictorVerifier* verifier); + + // Used when predicting pages that will be used near browser startup. All + // arguments are the same as for PredictInternal. Returns true if any + // predictions were queued up. + bool PredictForStartup(nsICacheEntry* entry, bool fullUri, + nsINetworkPredictorVerifier* verifier); + + // Utilities related to prediction + + // Used to update our rolling load count (how many of the last n loads was a + // partular resource loaded on?) + // * entry - cache entry of page we're loading + // * flags - value that contains our rolling count as the top 20 bits (but + // we may use fewer than those 20 bits for calculations) + // * key - metadata key that we will update on entry + // * hitCount - part of the metadata we need to preserve + // * lastHit - part of the metadata we need to preserve + void UpdateRollingLoadCount(nsICacheEntry* entry, const uint32_t flags, + const char* key, const uint32_t hitCount, + const uint32_t lastHit); + + // Used to calculate how much to degrade our confidence for all resources + // on a particular page, because of how long ago the most recent load of that + // page was. Returns a value between 0 (very recent most recent load) and 100 + // (very distant most recent load) + // * lastLoad - time stamp of most recent load of a page + int32_t CalculateGlobalDegradation(uint32_t lastLoad); + + // Used to calculate how confident we are that a particular resource will be + // used. Returns a value between 0 (no confidence) and 100 (very confident) + // * hitCount - number of times this resource has been seen when loading + // this page + // * hitsPossible - number of times this page has been loaded + // * lastHit - timestamp of the last time this resource was seen when + // loading this page + // * lastPossible - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation); + + // Used to calculate all confidence values for all resources associated with a + // page. + // * entry - the cache entry with all necessary information about this page + // * referrer - the URI that we are loading (may be null) + // * lastLoad - timestamp of the last time this page was loaded + // * loadCount - number of times this page has been loaded + // * gloablDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * fullUri - whether we're predicting for a full URI or origin-only + void CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri); + + enum PrefetchIgnoreReason { + PREFETCH_OK, + NOT_FULL_URI, + NO_REFERRER, + MISSED_A_LOAD, + PREFETCH_DISABLED, + PREFETCH_DISABLED_VIA_COUNT, + CONFIDENCE_TOO_LOW + }; + + // Used to prepare any necessary prediction for a resource on a page + // * confidence - value calculated by CalculateConfidence for this resource + // * flags - the flags taken from the resource + // * uri - the ascii spec of the URI of the resource + void SetupPrediction(int32_t confidence, uint32_t flags, const nsCString& uri, + PrefetchIgnoreReason reason); + + // Used to kick off a prefetch from RunPredictions if necessary + // * uri - the URI to prefetch + // * referrer - the URI of the referring page + // * originAttributes - the originAttributes of this prefetch + // * verifier - used for testing to ensure the expected prefetch happens + nsresult Prefetch(nsIURI* uri, nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used to actually perform any predictions set up via SetupPrediction. + // Returns true if any predictions were performed. + // * referrer - the URI we are predicting from + // * originAttributs - the originAttributes we are predicting from + // * verifier - used for testing to ensure the expected predictions happen + bool RunPredictions(nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used to guess whether a page will redirect to another page or not. Returns + // true if a redirection is likely. + // * entry - cache entry with all necessary information about this page + // * loadCount - number of times this page has been loaded + // * lastLoad - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * redirectURI - if this returns true, the URI that is likely to be + // redirected to, otherwise null + bool WouldRedirect(nsICacheEntry* entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI** redirectURI); + + // The guts of learning information + + // This is the top-level driver for doing any updating of our information in + // the cache + // * reason - why this learn is happening (pageload, startup, redirect) + // * entry - the cache entry with the information we need + // * isNew - whether or not the cache entry is brand new and empty + // * fullUri - whether we are doing predictions based on a full page URI, or + // just the origin of the page + // * targetURI - the URI that we are adding to our data - most often a + // resource loaded by a page the user navigated to + // * sourceURI - the URI that caused targetURI to be loaded, usually the + // page the user navigated to + void LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsIURI* sourceURI); + + // Used when learning about a resource loaded by a page + // * entry - the cache entry with information that needs updating + // * targetURI - the URI of the resource that was loaded by the page + void LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI); + + // Used when learning about a redirect from one page to another + // * entry - the cache entry of the page that was redirected from + // * targetURI - the URI of the redirect target + void LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI); + + // Used to learn about pages loaded close to browser startup. This results in + // LearnForStartup being called if we are, in fact, near browser startup + // * uri - the URI of a page that has been loaded (may not have been near + // browser startup) + // * fullUri - true if this is a full page uri, false if it's an origin + // * originAttributes - the originAttributes for this learning. + void MaybeLearnForStartup(nsIURI* uri, bool fullUri, + const OriginAttributes& originAttributes); + + // Used in conjunction with MaybeLearnForStartup to learn about pages loaded + // close to browser startup + // * entry - the cache entry that stores the startup page list + // * targetURI - the URI of a page that was loaded near browser startup + void LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI); + + // Used to parse the data we store in cache metadata + // * key - the cache metadata key + // * value - the cache metadata value + // * uri - (out) the ascii spec of the URI this metadata entry was about + // * hitCount - (out) the number of times this URI has been seen + // * lastHit - (out) timestamp of the last time this URI was seen + // * flags - (out) flags for this metadata entry + bool ParseMetaDataEntry(const char* key, const char* value, nsCString& uri, + uint32_t& hitCount, uint32_t& lastHit, + uint32_t& flags); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + void UpdateCacheabilityInternal(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, const nsCString& method, + const OriginAttributes& originAttributes, + bool isTracking, bool couldVary, + bool isNoStore); + + // Gets the pref value and clamps it within the acceptable range. + uint32_t ClampedPrefetchRollingLoadCount(); + + // Our state + bool mInitialized{false}; + + nsTArray mKeysToOperateOn; + nsTArray mValuesToOperateOn; + + nsCOMPtr mCacheStorageService; + + nsCOMPtr mSpeculativeService; + + nsCOMPtr mStartupURI; + uint32_t mStartupTime{0}; + uint32_t mLastStartupTime{0}; + int32_t mStartupCount{1}; + + nsCOMPtr mDnsService; + + RefPtr mDNSListener; + + nsTArray> mPrefetches; + nsTArray> mPreconnects; + nsTArray> mPreresolves; + + static Predictor* sSelf; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Predictor_h diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h new file mode 100644 index 0000000000..a2e224f092 --- /dev/null +++ b/netwerk/base/PrivateBrowsingChannel.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_PrivateBrowsingChannel_h__ +#define mozilla_net_PrivateBrowsingChannel_h__ + +#include "nsIPrivateBrowsingChannel.h" +#include "nsCOMPtr.h" +#include "nsILoadGroup.h" +#include "nsILoadContext.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInterfaceRequestor.h" +#include "nsNetUtil.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace net { + +template +class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel { + public: + PrivateBrowsingChannel() + : mPrivateBrowsingOverriden(false), mPrivateBrowsing(false) {} + + NS_IMETHOD SetPrivate(bool aPrivate) override { + // Make sure that we don't have a load context + // This is a fatal error in debug builds, and a runtime error in release + // builds. + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(static_cast(this), loadContext); + MOZ_ASSERT(!loadContext); + if (loadContext) { + return NS_ERROR_FAILURE; + } + + mPrivateBrowsingOverriden = true; + mPrivateBrowsing = aPrivate; + return NS_OK; + } + + NS_IMETHOD GetIsChannelPrivate(bool* aResult) override { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsing; + return NS_OK; + } + + NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool* aResult) override { + NS_ENSURE_ARG_POINTER(aValue); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsingOverriden; + if (mPrivateBrowsingOverriden) { + *aValue = mPrivateBrowsing; + } + return NS_OK; + } + + // Must be called every time the channel's callbacks or loadGroup is updated + void UpdatePrivateBrowsing() { + // once marked as private we never go un-private + if (mPrivateBrowsing) { + return; + } + + auto channel = static_cast(this); + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(channel, loadContext); + if (loadContext) { + mPrivateBrowsing = loadContext->UsePrivateBrowsing(); + return; + } + + nsCOMPtr loadInfo = channel->LoadInfo(); + OriginAttributes attrs = loadInfo->GetOriginAttributes(); + mPrivateBrowsing = attrs.mPrivateBrowsingId > 0; + } + + bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const { + // Make sure that the private bit override flag is not set. + // This is a fatal error in debug builds, and a runtime error in release + // builds. + if (!aCallbacks) { + return true; + } + nsCOMPtr 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 callbacks; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + // From this point on, we just hand off the work to CanSetCallbacks, + // because the logic is exactly the same. + return CanSetCallbacks(callbacks); + } + + protected: + bool mPrivateBrowsingOverriden; + bool mPrivateBrowsing; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ProtocolHandlerInfo.cpp b/netwerk/base/ProtocolHandlerInfo.cpp new file mode 100644 index 0000000000..432ff965ce --- /dev/null +++ b/netwerk/base/ProtocolHandlerInfo.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProtocolHandlerInfo.h" +#include "StaticComponents.h" +#include "nsIProtocolHandler.h" + +namespace mozilla::net { + +uint32_t ProtocolHandlerInfo::StaticProtocolFlags() const { + uint32_t flags = mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mProtocolFlags; + }, + [&](const RuntimeProtocolHandler& handler) { + return handler.mProtocolFlags; + }); +#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED + MOZ_RELEASE_ASSERT(!(flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC), + "ORIGIN_IS_FULL_SPEC is unsupported but used"); +#endif + return flags; +} + +int32_t ProtocolHandlerInfo::DefaultPort() const { + return mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mDefaultPort; + }, + [&](const RuntimeProtocolHandler& handler) { + return handler.mDefaultPort; + }); +} + +nsresult ProtocolHandlerInfo::DynamicProtocolFlags(nsIURI* aURI, + uint32_t* aFlags) const { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + // If we're querying dynamic flags, we'll need to fetch the actual xpcom + // component in order to check them. + if (HasDynamicFlags()) { + nsCOMPtr handler = Handler(); + if (nsCOMPtr dynamic = + do_QueryInterface(handler)) { + nsresult rv = dynamic->GetFlagsForURI(aURI, aFlags); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_DIAGNOSTIC_ASSERT( + (StaticProtocolFlags() & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS) == + (*aFlags & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS), + "only DYNAMIC_URI_FLAGS may be changed by a " + "nsIProtocolHandlerWithDynamicFlags implementation"); + return NS_OK; + } + } + + // Otherwise, just check against static flags. + *aFlags = StaticProtocolFlags(); + return NS_OK; +} + +bool ProtocolHandlerInfo::HasDynamicFlags() const { + return mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mHasDynamicFlags; + }, + [&](const RuntimeProtocolHandler&) { return false; }); +} + +already_AddRefed ProtocolHandlerInfo::Handler() const { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr retval; + mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + retval = handler->Module().GetService(); + }, + [&](const RuntimeProtocolHandler& handler) { + retval = handler.mHandler.get(); + }); + return retval.forget(); +} + +} // namespace mozilla::net diff --git a/netwerk/base/ProtocolHandlerInfo.h b/netwerk/base/ProtocolHandlerInfo.h new file mode 100644 index 0000000000..337dbddcfc --- /dev/null +++ b/netwerk/base/ProtocolHandlerInfo.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ProtocolHandlerInfo_h +#define mozilla_net_ProtocolHandlerInfo_h + +#include "mozilla/Variant.h" +#include "nsProxyRelease.h" +#include "nsIProtocolHandler.h" + +namespace mozilla { +namespace xpcom { +struct StaticProtocolHandler; +} + +namespace net { + +struct RuntimeProtocolHandler { + nsMainThreadPtrHandle mHandler; + uint32_t mProtocolFlags; + int32_t mDefaultPort; +}; + +// Information about a specific protocol handler. +class ProtocolHandlerInfo { + public: + explicit ProtocolHandlerInfo(const xpcom::StaticProtocolHandler& aStatic) + : mInner(AsVariant(&aStatic)) {} + explicit ProtocolHandlerInfo(RuntimeProtocolHandler aDynamic) + : mInner(AsVariant(std::move(aDynamic))) {} + + // Returns the statically known protocol-specific flags. + // See `nsIProtocolHandler` for valid values. + uint32_t StaticProtocolFlags() const; + + // The port that this protocol normally uses. + // If a port does not make sense for the protocol (e.g., "about:") then -1 + // will be returned. + int32_t DefaultPort() const; + + // If true, `DynamicProtocolFlags()` may return a different value than + // `StaticProtocolFlags()` for flags in `DYNAMIC_URI_FLAGS`, due to a + // `nsIProtocolHandlerWithDynamicFlags` implementation. + bool HasDynamicFlags() const; + + // Like `StaticProtocolFlags()` but also checks + // `nsIProtocolHandlerWithDynamicFlags` for uri-specific flags. + // + // NOTE: Only safe to call from the main thread. + nsresult DynamicProtocolFlags(nsIURI* aURI, uint32_t* aFlags) const + MOZ_REQUIRES(sMainThreadCapability); + + // Get the main-thread-only nsIProtocolHandler instance. + already_AddRefed Handler() const + MOZ_REQUIRES(sMainThreadCapability); + + private: + Variant mInner; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ProtocolHandlerInfo_h diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp new file mode 100644 index 0000000000..e5c585ec1a --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -0,0 +1,940 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProxyAutoConfig.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsINamed.h" +#include "nsThreadUtils.h" +#include "nsIConsoleService.h" +#include "nsIURLParser.h" +#include "nsJSUtils.h" +#include "jsfriendapi.h" +#include "js/CallAndConstruct.h" // JS_CallFunctionName +#include "js/CompilationAndEvaluation.h" // JS::Compile +#include "js/ContextOptions.h" +#include "js/Initialization.h" +#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty +#include "js/PropertySpec.h" +#include "js/SourceText.h" // JS::Source{Ownership,Text} +#include "js/Utility.h" +#include "js/Warnings.h" // JS::SetWarningReporter +#include "prnetdb.h" +#include "nsITimer.h" +#include "mozilla/Atomics.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/DNS.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/net/ProxyAutoConfigChild.h" +#include "mozilla/net/ProxyAutoConfigParent.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "nsServiceManagerUtils.h" +#include "nsNetCID.h" + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +#endif + +#include "XPCSelfHostedShmem.h" + +namespace mozilla { +namespace net { + +// These are some global helper symbols the PAC format requires that we provide +// that are initialized as part of the global javascript context used for PAC +// evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in +// the same context but are implemented as c++ helpers. alert(msg) is similarly +// defined. +// +// Per ProxyAutoConfig::Init, this data must be ASCII. + +static const char sAsciiPacUtils[] = +#include "ascii_pac_utils.inc" + ; + +// sRunning is defined for the helper functions only while the +// Javascript engine is running and the PAC object cannot be deleted +// or reset. +static Atomic& RunningIndex() { + static Atomic sRunningIndex(0xdeadbeef); + return sRunningIndex; +} +static ProxyAutoConfig* GetRunning() { + MOZ_ASSERT(RunningIndex() != 0xdeadbeef); + return static_cast(PR_GetThreadPrivate(RunningIndex())); +} + +static void SetRunning(ProxyAutoConfig* arg) { + MOZ_ASSERT(RunningIndex() != 0xdeadbeef); + MOZ_DIAGNOSTIC_ASSERT_IF(!arg, GetRunning() != nullptr); + MOZ_DIAGNOSTIC_ASSERT_IF(arg, GetRunning() == nullptr); + PR_SetThreadPrivate(RunningIndex(), arg); +} + +// The PACResolver is used for dnsResolve() +class PACResolver final : public nsIDNSListener, + public nsITimerCallback, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit PACResolver(nsIEventTarget* aTarget) + : mStatus(NS_ERROR_FAILURE), + mMainThreadEventTarget(aTarget), + mMutex("PACResolver::Mutex") {} + + // nsIDNSListener + NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record, + nsresult status) override { + nsCOMPtr timer; + { + MutexAutoLock lock(mMutex); + timer.swap(mTimer); + mRequest = nullptr; + } + + if (timer) { + timer->Cancel(); + } + + mStatus = status; + mResponse = record; + return NS_OK; + } + + // nsITimerCallback + NS_IMETHOD Notify(nsITimer* timer) override { + nsCOMPtr request; + { + MutexAutoLock lock(mMutex); + request.swap(mRequest); + mTimer = nullptr; + } + if (request) { + request->Cancel(NS_ERROR_NET_TIMEOUT); + } + return NS_OK; + } + + // nsINamed + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("PACResolver"); + return NS_OK; + } + + nsresult mStatus; + nsCOMPtr mRequest; + nsCOMPtr mResponse; + nsCOMPtr mTimer; + nsCOMPtr mMainThreadEventTarget; + Mutex mMutex MOZ_UNANNOTATED; + + private: + ~PACResolver() = default; +}; +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed) + +static void PACLogToConsole(nsString& aMessage) { + if (XRE_IsSocketProcess()) { + auto task = [message(aMessage)]() { + SocketProcessChild* child = SocketProcessChild::GetSingleton(); + if (child) { + Unused << child->SendOnConsoleMessage(message); + } + }; + if (NS_IsMainThread()) { + task(); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction("PACLogToConsole", task)); + } + return; + } + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!consoleService) return; + + consoleService->LogStringMessage(aMessage.get()); +} + +// Javascript errors and warnings are logged to the main error console +static void PACLogErrorOrWarning(const nsAString& aKind, + JSErrorReport* aReport) { + nsString formattedMessage(u"PAC Execution "_ns); + formattedMessage += aKind; + formattedMessage += u": "_ns; + if (aReport->message()) { + formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + } + formattedMessage += u" ["_ns; + formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); + formattedMessage += u"]"_ns; + PACLogToConsole(formattedMessage); +} + +static void PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) { + MOZ_ASSERT(aReport); + MOZ_ASSERT(aReport->isWarning()); + + PACLogErrorOrWarning(u"Warning"_ns, aReport); +} + +class MOZ_STACK_CLASS AutoPACErrorReporter { + JSContext* mCx; + + public: + explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {} + ~AutoPACErrorReporter() { + if (!JS_IsExceptionPending(mCx)) { + return; + } + JS::ExceptionStack exnStack(mCx); + if (!JS::StealPendingExceptionStack(mCx, &exnStack)) { + return; + } + + JS::ErrorReportBuilder report(mCx); + if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { + JS_ClearPendingException(mCx); + return; + } + + PACLogErrorOrWarning(u"Error"_ns, report.report()); + } +}; + +// timeout of 0 means the normal necko timeout strategy, otherwise the dns +// request will be canceled after aTimeout milliseconds +static bool PACResolve(const nsACString& aHostName, NetAddr* aNetAddr, + unsigned int aTimeout) { + if (!GetRunning()) { + NS_WARNING("PACResolve without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout); +} + +ProxyAutoConfig::ProxyAutoConfig() + +{ + MOZ_COUNT_CTOR(ProxyAutoConfig); +} + +bool ProxyAutoConfig::ResolveAddress(const nsACString& aHostName, + NetAddr* aNetAddr, unsigned int aTimeout) { + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) return false; + + RefPtr helper = new PACResolver(mMainThreadEventTarget); + OriginAttributes attrs; + + // When the PAC script attempts to resolve a domain, we must make sure we + // don't use TRR, otherwise the TRR channel might also attempt to resolve + // a name and we'll have a deadlock. + nsIDNSService::DNSFlags flags = + nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE); + + if (NS_FAILED(dns->AsyncResolveNative( + aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr, + helper, GetCurrentSerialEventTarget(), attrs, + getter_AddRefs(helper->mRequest)))) { + return false; + } + + if (aTimeout && helper->mRequest) { + if (!mTimer) mTimer = NS_NewTimer(); + if (mTimer) { + mTimer->SetTarget(mMainThreadEventTarget); + mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT); + helper->mTimer = mTimer; + } + } + + // Spin the event loop of the pac thread until lookup is complete. + // nsPACman is responsible for keeping a queue and only allowing + // one PAC execution at a time even when it is called re-entrantly. + SpinEventLoopUntil("ProxyAutoConfig::ResolveAddress"_ns, [&, helper, this]() { + if (!helper->mRequest) { + return true; + } + if (this->mShutdown) { + NS_WARNING("mShutdown set with PAC request not cancelled"); + MOZ_ASSERT(NS_FAILED(helper->mStatus)); + return true; + } + return false; + }); + + if (NS_FAILED(helper->mStatus)) { + return false; + } + + nsCOMPtr rec = do_QueryInterface(helper->mResponse); + return !(!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr))); +} + +static bool PACResolveToString(const nsACString& aHostName, + nsCString& aDottedDecimal, + unsigned int aTimeout) { + NetAddr netAddr; + if (!PACResolve(aHostName, &netAddr, aTimeout)) return false; + + char dottedDecimal[128]; + if (!netAddr.ToStringBuffer(dottedDecimal, sizeof(dottedDecimal))) { + return false; + } + + aDottedDecimal.Assign(dottedDecimal); + return true; +} + +// dnsResolve(host) javascript implementation +static bool PACDnsResolve(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false; + + // Previously we didn't check the type of the argument, so just converted it + // to string. A badly written PAC file oculd pass null or undefined here + // which could lead to odd results if there are any hosts called "null" + // on the network. See bug 1724345 comment 6. + if (!args[0].isString()) { + args.rval().setNull(); + return true; + } + + JS::Rooted arg1(cx); + arg1 = args[0].toString(); + + nsAutoJSString hostName; + nsAutoCString dottedDecimal; + + if (!hostName.init(cx, arg1)) return false; + if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) { + JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + args.rval().setString(dottedDecimalString); + } else { + args.rval().setNull(); + } + + return true; +} + +// myIpAddress() javascript implementation +static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!GetRunning()) { + NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->MyIPAddress(args); +} + +// proxyAlert(msg) javascript implementation +static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "alert", 1)) return false; + + JS::Rooted arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) return false; + + nsAutoJSString message; + if (!message.init(cx, arg1)) return false; + + nsAutoString alertMessage; + alertMessage.AssignLiteral(u"PAC-alert: "); + alertMessage.Append(message); + PACLogToConsole(alertMessage); + + args.rval().setUndefined(); /* return undefined */ + return true; +} + +static const JSFunctionSpec PACGlobalFunctions[] = { + JS_FN("dnsResolve", PACDnsResolve, 1, 0), + + // a global "var pacUseMultihomedDNS = true;" will change behavior + // of myIpAddress to actively use DNS + JS_FN("myIpAddress", PACMyIpAddress, 0, 0), + JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END}; + +// JSContextWrapper is a c++ object that manages the context for the JS engine +// used on the PAC thread. It is initialized and destroyed on the PAC thread. +class JSContextWrapper { + public: + static JSContextWrapper* Create(uint32_t aExtraHeapSize) { + JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes + aExtraHeapSize); + if (NS_WARN_IF(!cx)) return nullptr; + + JS::ContextOptionsRef(cx).setDisableIon().setDisableEvalSecurityChecks(); + + JSContextWrapper* entry = new JSContextWrapper(cx); + if (NS_FAILED(entry->Init())) { + delete entry; + return nullptr; + } + + return entry; + } + + JSContext* Context() const { return mContext; } + + JSObject* Global() const { return mGlobal; } + + ~JSContextWrapper() { + mGlobal = nullptr; + + MOZ_COUNT_DTOR(JSContextWrapper); + + if (mContext) { + JS_DestroyContext(mContext); + } + } + + void SetOK() { mOK = true; } + + bool IsOK() { return mOK; } + + private: + JSContext* mContext; + JS::PersistentRooted mGlobal; + bool mOK; + + static const JSClass sGlobalClass; + + explicit JSContextWrapper(JSContext* cx) + : mContext(cx), mGlobal(cx, nullptr), mOK(false) { + MOZ_COUNT_CTOR(JSContextWrapper); + } + + nsresult Init() { + /* + * Not setting this will cause JS_CHECK_RECURSION to report false + * positives + */ + JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024); + + JS::SetWarningReporter(mContext, PACWarningReporter); + + // When available, set the self-hosted shared memory to be read, so that + // we can decode the self-hosted content instead of parsing it. + { + auto& shm = xpc::SelfHostedShmem::GetSingleton(); + JS::SelfHostedCache selfHostedContent = shm.Content(); + + if (!JS::InitSelfHostedCode(mContext, selfHostedContent)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + JS::RealmOptions options; + options.creationOptions().setNewCompartmentInSystemZone(); + options.behaviors().setClampAndJitterTime(false); + mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, + JS::DontFireOnNewGlobalHook, options); + if (!mGlobal) { + JS_ClearPendingException(mContext); + return NS_ERROR_OUT_OF_MEMORY; + } + JS::Rooted global(mContext, mGlobal); + + JSAutoRealm ar(mContext, global); + AutoPACErrorReporter aper(mContext); + if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { + return NS_ERROR_FAILURE; + } + + JS_FireOnNewGlobalObject(mContext, global); + + return NS_OK; + } +}; + +const JSClass JSContextWrapper::sGlobalClass = {"PACResolutionThreadGlobal", + JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + +void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) { + RunningIndex() = index; +} + +nsresult ProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) { + mShutdown = false; // Shutdown needs to be called prior to destruction + + mPACURI = aPACURI; + + // The full PAC script data is the concatenation of 1) the various functions + // exposed to PAC scripts in |sAsciiPacUtils| and 2) the user-provided PAC + // script data. Historically this was single-byte Latin-1 text (usually just + // ASCII, but bug 296163 has a real-world Latin-1 example). We now support + // UTF-8 if the full data validates as UTF-8, before falling back to Latin-1. + // (Technically this is a breaking change: intentional Latin-1 scripts that + // happen to be valid UTF-8 may have different behavior. We assume such cases + // are vanishingly rare.) + // + // Supporting both UTF-8 and Latin-1 requires that the functions exposed to + // PAC scripts be both UTF-8- and Latin-1-compatible: that is, they must be + // ASCII. + mConcatenatedPACData = sAsciiPacUtils; + if (!mConcatenatedPACData.Append(aPACScriptData, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mIncludePath = aIncludePath; + mExtraHeapSize = aExtraHeapSize; + mMainThreadEventTarget = aEventTarget; + + if (!GetRunning()) return SetupJS(); + + mJSNeedsSetup = true; + return NS_OK; +} + +nsresult ProxyAutoConfig::SetupJS() { + mJSNeedsSetup = false; + MOZ_DIAGNOSTIC_ASSERT(!GetRunning(), "JIT is running"); + if (GetRunning()) { + return NS_ERROR_ALREADY_INITIALIZED; + } + +#if defined(XP_MACOSX) + nsMacUtilsImpl::EnableTCSMIfAvailable(); +#endif + + delete mJSContext; + mJSContext = nullptr; + + if (mConcatenatedPACData.IsEmpty()) return NS_ERROR_FAILURE; + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + mJSContext = JSContextWrapper::Create(mExtraHeapSize); + if (!mJSContext) return NS_ERROR_FAILURE; + + JSContext* cx = mJSContext->Context(); + JSAutoRealm ar(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // check if this is a data: uri so that we don't spam the js console with + // huge meaningless strings. this is not on the main thread, so it can't + // use nsIURI scheme methods + bool isDataURI = + nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5); + + SetRunning(this); + + JS::Rooted global(cx, mJSContext->Global()); + + auto CompilePACScript = [this](JSContext* cx) -> JSScript* { + JS::CompileOptions options(cx); + options.setSkipFilenameValidation(true); + options.setFileAndLine(this->mPACURI.get(), 1); + + // Per ProxyAutoConfig::Init, compile as UTF-8 if the full data is UTF-8, + // and otherwise inflate Latin-1 to UTF-16 and compile that. + const char* scriptData = this->mConcatenatedPACData.get(); + size_t scriptLength = this->mConcatenatedPACData.Length(); + if (mozilla::IsUtf8(mozilla::Span(scriptData, scriptLength))) { + JS::SourceText srcBuf; + if (!srcBuf.init(cx, scriptData, scriptLength, + JS::SourceOwnership::Borrowed)) { + return nullptr; + } + + return JS::Compile(cx, options, srcBuf); + } + + // nsReadableUtils.h says that "ASCII" is a misnomer "for legacy reasons", + // and this handles not just ASCII but Latin-1 too. + NS_ConvertASCIItoUTF16 inflated(this->mConcatenatedPACData); + + JS::SourceText source; + if (!source.init(cx, inflated.get(), inflated.Length(), + JS::SourceOwnership::Borrowed)) { + return nullptr; + } + + return JS::Compile(cx, options, source); + }; + + JS::Rooted script(cx, CompilePACScript(cx)); + if (!script || !JS_ExecuteScript(cx, script)) { + nsString alertMessage(u"PAC file failed to install from "_ns); + if (isDataURI) { + alertMessage += u"data: URI"_ns; + } else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + SetRunning(nullptr); + return NS_ERROR_FAILURE; + } + SetRunning(nullptr); + + mJSContext->SetOK(); + nsString alertMessage(u"PAC file installed from "_ns); + if (isDataURI) { + alertMessage += u"data: URI"_ns; + } else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + + // we don't need these now + mConcatenatedPACData.Truncate(); + mPACURI.Truncate(); + + return NS_OK; +} + +void ProxyAutoConfig::GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function&& + aCallback) { + nsAutoCString result; + nsresult status = GetProxyForURI(aTestURI, aTestHost, result); + aCallback(status, result); +} + +nsresult ProxyAutoConfig::GetProxyForURI(const nsACString& aTestURI, + const nsACString& aTestHost, + nsACString& result) { + if (mJSNeedsSetup) SetupJS(); + + if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE; + + JSContext* cx = mJSContext->Context(); + JSAutoRealm ar(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // the sRunning flag keeps a new PAC file from being installed + // while the event loop is spinning on a DNS function. Don't early return. + SetRunning(this); + mRunningHost = aTestHost; + + nsresult rv = NS_ERROR_FAILURE; + nsCString clensedURI(aTestURI); + + if (!mIncludePath) { + nsCOMPtr urlParser = + do_GetService(NS_STDURLPARSER_CONTRACTID); + int32_t pathLen = 0; + if (urlParser) { + uint32_t schemePos; + int32_t schemeLen; + uint32_t authorityPos; + int32_t authorityLen; + uint32_t pathPos; + rv = urlParser->ParseURL(aTestURI.BeginReading(), aTestURI.Length(), + &schemePos, &schemeLen, &authorityPos, + &authorityLen, &pathPos, &pathLen); + } + if (NS_SUCCEEDED(rv)) { + if (pathLen) { + // cut off the path but leave the initial slash + pathLen--; + } + aTestURI.Left(clensedURI, aTestURI.Length() - pathLen); + } + } + + JS::Rooted uriString( + cx, + JS_NewStringCopyN(cx, clensedURI.BeginReading(), clensedURI.Length())); + JS::Rooted hostString( + cx, JS_NewStringCopyN(cx, aTestHost.BeginReading(), aTestHost.Length())); + + if (uriString && hostString) { + JS::RootedValueArray<2> args(cx); + args[0].setString(uriString); + args[1].setString(hostString); + + JS::Rooted rval(cx); + JS::Rooted global(cx, mJSContext->Global()); + bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval); + + if (ok && rval.isString()) { + nsAutoJSString pacString; + if (pacString.init(cx, rval.toString())) { + CopyUTF16toUTF8(pacString, result); + rv = NS_OK; + } + } + } + + mRunningHost.Truncate(); + SetRunning(nullptr); + return rv; +} + +void ProxyAutoConfig::GC() { + if (!mJSContext || !mJSContext->IsOK()) return; + + JSAutoRealm ar(mJSContext->Context(), mJSContext->Global()); + JS_MaybeGC(mJSContext->Context()); +} + +ProxyAutoConfig::~ProxyAutoConfig() { + MOZ_COUNT_DTOR(ProxyAutoConfig); + MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor."); + NS_ASSERTION(!mJSContext, + "~ProxyAutoConfig leaking JS context that " + "should have been deleted on pac thread"); +} + +void ProxyAutoConfig::Shutdown() { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown"); + + if (NS_WARN_IF(GetRunning()) || mShutdown) { + return; + } + + mShutdown = true; + delete mJSContext; + mJSContext = nullptr; +} + +bool ProxyAutoConfig::SrcAddress(const NetAddr* remoteAddress, + nsCString& localAddress) { + PRFileDesc* fd; + fd = PR_OpenUDPSocket(remoteAddress->raw.family); + if (!fd) return false; + + PRNetAddr prRemoteAddress; + NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress); + if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PRNetAddr localName; + if (PR_GetSockName(fd, &localName) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PR_Close(fd); + + char dottedDecimal[128]; + if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != + PR_SUCCESS) { + return false; + } + + localAddress.Assign(dottedDecimal); + + return true; +} + +// hostName is run through a dns lookup and then a udp socket is connected +// to the result. If that all works, the local IP address of the socket is +// returned to the javascript caller and |*aResult| is set to true. Otherwise +// |*aResult| is set to false. +bool ProxyAutoConfig::MyIPAddressTryHost(const nsACString& hostName, + unsigned int timeout, + const JS::CallArgs& aArgs, + bool* aResult) { + *aResult = false; + + NetAddr remoteAddress; + nsAutoCString localDottedDecimal; + JSContext* cx = mJSContext->Context(); + + if (PACResolve(hostName, &remoteAddress, timeout) && + SrcAddress(&remoteAddress, localDottedDecimal)) { + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + *aResult = true; + aArgs.rval().setString(dottedDecimalString); + } + return true; +} + +bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) { + nsAutoCString remoteDottedDecimal; + nsAutoCString localDottedDecimal; + JSContext* cx = mJSContext->Context(); + JS::Rooted v(cx); + JS::Rooted global(cx, mJSContext->Global()); + + bool useMultihomedDNS = + JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) && + !v.isUndefined() && ToBoolean(v); + + // first, lookup the local address of a socket connected + // to the host of uri being resolved by the pac file. This is + // v6 safe.. but is the last step like that + bool rvalAssigned = false; + if (useMultihomedDNS) { + if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + } else { + // we can still do the fancy multi homing thing if the host is a literal + if (HostIsIPLiteral(mRunningHost) && + (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned)) { + return rvalAssigned; + } + } + + // next, look for a route to a public internet address that doesn't need DNS. + // This is the google anycast dns address, but it doesn't matter if it + // remains operable (as we don't contact it) as long as the address stays + // in commonly routed IP address space. + remoteDottedDecimal.AssignLiteral("8.8.8.8"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // finally, use the old algorithm based on the local hostname + nsAutoCString hostName; + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + // without multihomedDNS use such a short timeout that we are basically + // just looking at the cache for raw dotted decimals + uint32_t timeout = useMultihomedDNS ? kTimeout : 1; + if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) && + PACResolveToString(hostName, localDottedDecimal, timeout)) { + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; + } + + // next try a couple RFC 1918 variants.. maybe there is a + // local route + remoteDottedDecimal.AssignLiteral("192.168.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // more RFC 1918 + remoteDottedDecimal.AssignLiteral("10.0.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // who knows? let's fallback to localhost + localDottedDecimal.AssignLiteral("127.0.0.1"); + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; +} + +RemoteProxyAutoConfig::RemoteProxyAutoConfig() = default; + +RemoteProxyAutoConfig::~RemoteProxyAutoConfig() = default; + +nsresult RemoteProxyAutoConfig::Init(nsIThread* aPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + + SocketProcessParent* socketProcessParent = + SocketProcessParent::GetSingleton(); + if (!socketProcessParent) { + return NS_ERROR_NOT_AVAILABLE; + } + + ipc::Endpoint parent; + ipc::Endpoint child; + nsresult rv = PProxyAutoConfig::CreateEndpoints( + base::GetCurrentProcId(), socketProcessParent->OtherPid(), &parent, + &child); + if (NS_FAILED(rv)) { + return rv; + } + + Unused << socketProcessParent->SendInitProxyAutoConfigChild(std::move(child)); + mProxyAutoConfigParent = new ProxyAutoConfigParent(); + return aPACThread->Dispatch( + NS_NewRunnableFunction("ProxyAutoConfigParent::ProxyAutoConfigParent", + [proxyAutoConfigParent(mProxyAutoConfigParent), + endpoint{std::move(parent)}]() mutable { + proxyAutoConfigParent->Init(std::move(endpoint)); + })); +} + +nsresult RemoteProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget*) { + Unused << mProxyAutoConfigParent->SendConfigurePAC( + aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize); + return NS_OK; +} + +void RemoteProxyAutoConfig::Shutdown() { mProxyAutoConfigParent->Close(); } + +void RemoteProxyAutoConfig::GC() { + // Do nothing. GC would be performed when there is not pending query in socket + // process. +} + +void RemoteProxyAutoConfig::GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function&& + aCallback) { + if (!mProxyAutoConfigParent->CanSend()) { + return; + } + + mProxyAutoConfigParent->SendGetProxyForURI( + aTestURI, aTestHost, + [aCallback](std::tuple&& aResult) { + auto [status, result] = aResult; + aCallback(status, result); + }, + [aCallback](mozilla::ipc::ResponseRejectReason&& aReason) { + aCallback(NS_ERROR_FAILURE, ""_ns); + }); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h new file mode 100644 index 0000000000..e01450eadd --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ProxyAutoConfig_h__ +#define ProxyAutoConfig_h__ + +#include +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIEventTarget; +class nsITimer; +class nsIThread; +namespace JS { +class CallArgs; +} // namespace JS + +namespace mozilla { +namespace net { + +class JSContextWrapper; +class ProxyAutoConfigParent; +union NetAddr; + +class ProxyAutoConfigBase { + public: + virtual ~ProxyAutoConfigBase() = default; + virtual nsresult Init(nsIThread* aPACThread) { return NS_OK; } + virtual nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) = 0; + virtual void SetThreadLocalIndex(uint32_t index) {} + virtual void Shutdown() = 0; + virtual void GC() = 0; + virtual void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function&& + aCallback) = 0; +}; + +// The ProxyAutoConfig class is meant to be created and run on a +// non main thread. It synchronously resolves PAC files by blocking that +// thread and running nested event loops. GetProxyForURI is not re-entrant. + +class ProxyAutoConfig : public ProxyAutoConfigBase { + public: + ProxyAutoConfig(); + virtual ~ProxyAutoConfig(); + + nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) override; + void SetThreadLocalIndex(uint32_t index) override; + void Shutdown() override; + void GC() override; + bool MyIPAddress(const JS::CallArgs& aArgs); + bool ResolveAddress(const nsACString& aHostName, NetAddr* aNetAddr, + unsigned int aTimeout); + + /** + * Get the proxy string for the specified URI. The proxy string is + * given by the following: + * + * result = proxy-spec *( proxy-sep proxy-spec ) + * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port] + * direct-type = "DIRECT" + * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5" + * proxy-sep = ";" LWS + * proxy-host = hostname | ipv4-address-literal + * proxy-port = + * LWS = *( SP | HT ) + * SP = + * HT = + * + * NOTE: direct-type and proxy-type are case insensitive + * NOTE: SOCKS implies SOCKS4 + * + * Examples: + * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT" + * "SOCKS socksproxy" + * "DIRECT" + * + * XXX add support for IPv6 address literals. + * XXX quote whatever the official standard is for PAC. + * + * @param aTestURI + * The URI as an ASCII string to test. + * @param aTestHost + * The ASCII hostname to test. + * + * @param result + * result string as defined above. + */ + nsresult GetProxyForURI(const nsACString& aTestURI, + const nsACString& aTestHost, nsACString& result); + + void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function&& + aCallback) override; + + private: + // allow 665ms for myipaddress dns queries. That's 95th percentile. + const static unsigned int kTimeout = 665; + + // used to compile the PAC file and setup the execution context + nsresult SetupJS(); + + bool SrcAddress(const NetAddr* remoteAddress, nsCString& localAddress); + bool MyIPAddressTryHost(const nsACString& hostName, unsigned int timeout, + const JS::CallArgs& aArgs, bool* aResult); + + JSContextWrapper* mJSContext{nullptr}; + bool mJSNeedsSetup{false}; + bool mShutdown{true}; + nsCString mConcatenatedPACData; + nsCString mPACURI; + bool mIncludePath{false}; + uint32_t mExtraHeapSize{0}; + nsCString mRunningHost; + nsCOMPtr mTimer; + nsCOMPtr mMainThreadEventTarget; +}; + +class RemoteProxyAutoConfig : public ProxyAutoConfigBase { + public: + RemoteProxyAutoConfig(); + virtual ~RemoteProxyAutoConfig(); + + nsresult Init(nsIThread* aPACThread) override; + nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) override; + void Shutdown() override; + void GC() override; + void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function&& + aCallback) override; + + private: + RefPtr mProxyAutoConfigParent; +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfig_h__ diff --git a/netwerk/base/ProxyConfig.h b/netwerk/base/ProxyConfig.h new file mode 100644 index 0000000000..2deb1dd2a1 --- /dev/null +++ b/netwerk/base/ProxyConfig.h @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_netwerk_base_proxy_config_h +#define mozilla_netwerk_base_proxy_config_h + +#include + +#include "nsCRT.h" +#include "nsString.h" +#include "nsTArray.h" + +// NOTE: This file is inspired by Chromium's code. +// https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_config.h. + +namespace mozilla { +namespace net { + +// ProxyServer stores the {type, host, port} of a proxy server. +// ProxyServer is immutable. +class ProxyServer final { + public: + enum class ProxyType { + DIRECT = 0, + HTTP, + HTTPS, + SOCKS, + SOCKS4, + SOCKS5, + FTP, + // DEFAULT is a special type used on windows only. + DEFAULT + }; + + ProxyServer() = default; + + ProxyServer(ProxyType aType, const nsACString& aHost, int32_t aPort) + : mType(aType), mHost(aHost), mPort(aPort) {} + + const nsCString& Host() const { return mHost; } + + int32_t Port() const { return mPort; } + + ProxyType Type() const { return mType; } + + void ToHostAndPortStr(nsACString& aOutput) { + aOutput.Truncate(); + if (mType == ProxyType::DIRECT) { + return; + } + + aOutput.Assign(mHost); + if (mPort != -1) { + aOutput.Append(':'); + aOutput.AppendInt(mPort); + } + } + + bool operator==(const ProxyServer& aOther) const { + return mType == aOther.mType && mHost == aOther.mHost && + mPort == aOther.mPort; + } + + bool operator!=(const ProxyServer& aOther) const { + return !(*this == aOther); + } + + private: + ProxyType mType{ProxyType::DIRECT}; + nsCString mHost; + int32_t mPort{-1}; +}; + +// This class includes the information about proxy configuration. +// It contains enabled proxy servers, exception list, and the url of PAC +// script. +class ProxyConfig { + public: + struct ProxyRules { + ProxyRules() = default; + ~ProxyRules() = default; + + std::map mProxyServers; + }; + + struct ProxyBypassRules { + ProxyBypassRules() = default; + ~ProxyBypassRules() = default; + + CopyableTArray mExceptions; + }; + + ProxyConfig() = default; + ProxyConfig(const ProxyConfig& config); + ~ProxyConfig() = default; + + ProxyRules& Rules() { return mRules; } + + const ProxyRules& Rules() const { return mRules; } + + ProxyBypassRules& ByPassRules() { return mBypassRules; } + + const ProxyBypassRules& ByPassRules() const { return mBypassRules; } + + void SetPACUrl(const nsACString& aUrl) { mPACUrl = aUrl; } + + const nsCString& PACUrl() const { return mPACUrl; } + + static ProxyServer::ProxyType ToProxyType(const char* aType) { + if (!aType) { + return ProxyServer::ProxyType::DIRECT; + } + + if (nsCRT::strcasecmp(aType, "http") == 0) { + return ProxyServer::ProxyType::HTTP; + } + if (nsCRT::strcasecmp(aType, "https") == 0) { + return ProxyServer::ProxyType::HTTPS; + } + if (nsCRT::strcasecmp(aType, "socks") == 0) { + return ProxyServer::ProxyType::SOCKS; + } + if (nsCRT::strcasecmp(aType, "socks4") == 0) { + return ProxyServer::ProxyType::SOCKS4; + } + if (nsCRT::strcasecmp(aType, "socks5") == 0) { + return ProxyServer::ProxyType::SOCKS5; + } + if (nsCRT::strcasecmp(aType, "ftp") == 0) { + return ProxyServer::ProxyType::FTP; + } + + return ProxyServer::ProxyType::DIRECT; + } + + static void SetProxyResult(const char* aType, const nsACString& aHostPort, + nsACString& aResult) { + aResult.AssignASCII(aType); + aResult.Append(' '); + aResult.Append(aHostPort); + } + + static void SetProxyResultDirect(nsACString& aResult) { + // For whatever reason, a proxy is not to be used. + aResult.AssignLiteral("DIRECT"); + } + + static void ProxyStrToResult(const nsACString& aSpecificProxy, + const nsACString& aDefaultProxy, + const nsACString& aSocksProxy, + nsACString& aResult) { + if (!aSpecificProxy.IsEmpty()) { + SetProxyResult("PROXY", aSpecificProxy, + aResult); // Protocol-specific proxy. + } else if (!aDefaultProxy.IsEmpty()) { + SetProxyResult("PROXY", aDefaultProxy, aResult); // Default proxy. + } else if (!aSocksProxy.IsEmpty()) { + SetProxyResult("SOCKS", aSocksProxy, aResult); // SOCKS proxy. + } else { + SetProxyResultDirect(aResult); // Direct connection. + } + } + + void GetProxyString(const nsACString& aScheme, nsACString& aResult) { + SetProxyResultDirect(aResult); + + nsAutoCString specificProxy; + nsAutoCString defaultProxy; + nsAutoCString socksProxy; + nsAutoCString prefix; + ToLowerCase(aScheme, prefix); + ProxyServer::ProxyType type = ProxyConfig::ToProxyType(prefix.get()); + for (auto& [key, value] : mRules.mProxyServers) { + // Break the loop if we found a specific proxy. + if (key == type) { + value.ToHostAndPortStr(specificProxy); + break; + } else if (key == ProxyServer::ProxyType::DEFAULT) { + value.ToHostAndPortStr(defaultProxy); + } else if (key == ProxyServer::ProxyType::SOCKS) { + value.ToHostAndPortStr(socksProxy); + } + } + + ProxyStrToResult(specificProxy, defaultProxy, socksProxy, aResult); + } + + private: + nsCString mPACUrl; + ProxyRules mRules; + ProxyBypassRules mBypassRules; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_base_proxy_config_h diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp new file mode 100644 index 0000000000..9aa687ed88 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.cpp @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RedirectChannelRegistrar.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +StaticRefPtr RedirectChannelRegistrar::gSingleton; + +NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar) + +RedirectChannelRegistrar::RedirectChannelRegistrar() + : mRealChannels(32), + mParentChannels(32), + mLock("RedirectChannelRegistrar") { + MOZ_ASSERT(!gSingleton); +} + +// static +already_AddRefed +RedirectChannelRegistrar::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + if (!gSingleton) { + gSingleton = new RedirectChannelRegistrar(); + ClearOnShutdown(&gSingleton); + } + return do_AddRef(gSingleton); +} + +NS_IMETHODIMP +RedirectChannelRegistrar::RegisterChannel(nsIChannel* channel, uint64_t id) { + MutexAutoLock lock(mLock); + + mRealChannels.InsertOrUpdate(id, channel); + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetRegisteredChannel(uint64_t id, + nsIChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::LinkChannels(uint64_t id, nsIParentChannel* channel, + nsIChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + mParentChannels.InsertOrUpdate(id, channel); + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetParentChannel(uint64_t id, + nsIParentChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mParentChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::DeregisterChannels(uint64_t id) { + MutexAutoLock lock(mLock); + + mRealChannels.Remove(id); + mParentChannels.Remove(id); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h new file mode 100644 index 0000000000..5041726043 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.h @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RedirectChannelRegistrar_h__ +#define RedirectChannelRegistrar_h__ + +#include "nsIRedirectChannelRegistrar.h" + +#include "nsIChannel.h" +#include "nsIParentChannel.h" +#include "nsInterfaceHashtable.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar { + NS_DECL_ISUPPORTS + NS_DECL_NSIREDIRECTCHANNELREGISTRAR + + RedirectChannelRegistrar(); + + private: + ~RedirectChannelRegistrar() = default; + + public: + // Singleton accessor + static already_AddRefed GetOrCreate(); + + protected: + using ChannelHashtable = nsInterfaceHashtable; + using ParentChannelHashtable = + nsInterfaceHashtable; + + ChannelHashtable mRealChannels; + ParentChannelHashtable mParentChannels; + Mutex mLock MOZ_UNANNOTATED; + + static StaticRefPtr gSingleton; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp new file mode 100644 index 0000000000..72331bdc1e --- /dev/null +++ b/netwerk/base/RequestContextService.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIDocShell.h" +#include "mozilla/dom/Document.h" +#include "nsComponentManagerUtils.h" +#include "nsIDocumentLoader.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsIXULRuntime.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "RequestContextService.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Logging.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/PSpdyPush.h" + +#include "../protocol/http/nsHttpHandler.h" + +namespace mozilla { +namespace net { + +LazyLogModule gRequestContextLog("RequestContext"); +#undef LOG +#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args) + +static StaticRefPtr gSingleton; + +// This is used to prevent adding tail pending requests after shutdown +static bool sShutdown = false; + +// nsIRequestContext +class RequestContext final : public nsIRequestContext, + public nsITimerCallback, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXT + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + explicit RequestContext(const uint64_t id); + + private: + virtual ~RequestContext(); + + void ProcessTailQueue(nsresult aResult); + // Reschedules the timer if needed + void ScheduleUnblock(); + // Hard-reschedules the timer + void RescheduleUntailTimer(TimeStamp const& now); + + uint64_t mID; + Atomic mBlockingTransactionCount; + UniquePtr mSpdyCache; + + using PendingTailRequest = nsCOMPtr; + // Number of known opened non-tailed requets + uint32_t mNonTailRequests; + // Queue of requests that have been tailed, when conditions are met + // we call each of them to unblock and drop the reference + nsTArray mTailQueue; + // Loosly scheduled timer, never scheduled further to the future than + // mUntailAt time + nsCOMPtr mUntailTimer; + // Timestamp when the timer is expected to fire, + // always less than or equal to mUntailAt + TimeStamp mTimerScheduledAt; + // Timestamp when we want to actually untail queued requets based on + // the number of request count change in the past; iff this timestamp + // is set, we tail requests + TimeStamp mUntailAt; + + // Timestamp of the navigation start time, set to Now() in BeginLoad(). + // This is used to progressively lower the maximum delay time so that + // we can't get to a situation when a number of repetitive requests + // on the page causes forever tailing. + TimeStamp mBeginLoadTime; + + // This member is true only between DOMContentLoaded notification and + // next document load beginning for this request context. + // Top level request contexts are recycled. + bool mAfterDOMContentLoaded; +}; + +NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed) + +RequestContext::RequestContext(const uint64_t aID) + : mID(aID), + mBlockingTransactionCount(0), + mNonTailRequests(0), + mAfterDOMContentLoaded(false) { + LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID)); +} + +RequestContext::~RequestContext() { + MOZ_ASSERT(mTailQueue.Length() == 0); + + LOG(("RequestContext::~RequestContext this=%p blockers=%u", this, + static_cast(mBlockingTransactionCount))); +} + +NS_IMETHODIMP +RequestContext::BeginLoad() { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::BeginLoad %p", this)); + + if (IsNeckoChild()) { + // Tailing is not supported on the child process + if (gNeckoChild) { + gNeckoChild->SendRequestContextLoadBegin(mID); + } + return NS_OK; + } + + mAfterDOMContentLoaded = false; + mBeginLoadTime = TimeStamp::NowLoRes(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::DOMContentLoaded() { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::DOMContentLoaded %p", this)); + + if (IsNeckoChild()) { + // Tailing is not supported on the child process + if (gNeckoChild) { + gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID); + } + return NS_OK; + } + + if (mAfterDOMContentLoaded) { + // There is a possibility of a duplicate notification + return NS_OK; + } + + mAfterDOMContentLoaded = true; + + // Conditions for the delay calculation has changed. + ScheduleUnblock(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetBlockingTransactionCount( + uint32_t* aBlockingTransactionCount) { + NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); + *aBlockingTransactionCount = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::AddBlockingTransaction() { + mBlockingTransactionCount++; + LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this, + static_cast(mBlockingTransactionCount))); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveBlockingTransaction(uint32_t* outval) { + NS_ENSURE_ARG_POINTER(outval); + mBlockingTransactionCount--; + LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this, + static_cast(mBlockingTransactionCount))); + *outval = mBlockingTransactionCount; + return NS_OK; +} + +SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); } + +void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) { + mSpdyCache = WrapUnique(aSpdyPushCache); +} + +uint64_t RequestContext::GetID() { return mID; } + +NS_IMETHODIMP +RequestContext::AddNonTailRequest() { + MOZ_ASSERT(NS_IsMainThread()); + + ++mNonTailRequests; + LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this, + mNonTailRequests)); + + ScheduleUnblock(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveNonTailRequest() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mNonTailRequests > 0); + + LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this, + mNonTailRequests - 1)); + + --mNonTailRequests; + + ScheduleUnblock(); + return NS_OK; +} + +void RequestContext::ScheduleUnblock() { + MOZ_ASSERT(!IsNeckoChild()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!gHttpHandler) { + return; + } + + uint32_t quantum = + gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded); + uint32_t delayMax = gHttpHandler->TailBlockingDelayMax(); + uint32_t totalMax = gHttpHandler->TailBlockingTotalMax(); + + if (!mBeginLoadTime.IsNull()) { + // We decrease the maximum delay progressively with the time since the page + // load begin. This seems like a reasonable and clear heuristic allowing us + // to start loading tailed requests in a deterministic time after the load + // has started. + + uint32_t sinceBeginLoad = static_cast( + (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds()); + uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax); + uint32_t proportion = totalMax // values clamped between 0 and 60'000 + ? (delayMax * tillTotal) / totalMax + : 0; + delayMax = std::min(delayMax, proportion); + } + + CheckedInt delay = quantum * mNonTailRequests; + + if (!mAfterDOMContentLoaded) { + // Before DOMContentLoaded notification we want to make sure that tailed + // requests don't start when there is a short delay during which we may + // not have any active requests on the page happening. + delay += quantum; + } + + if (!delay.isValid() || delay.value() > delayMax) { + delay = delayMax; + } + + LOG( + ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu " + "delay=%u after-DCL=%d", + this, mNonTailRequests, mTailQueue.Length(), delay.value(), + mAfterDOMContentLoaded)); + + TimeStamp now = TimeStamp::NowLoRes(); + mUntailAt = now + TimeDuration::FromMilliseconds(delay.value()); + + if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) { + LOG(("RequestContext %p timer would fire too late, rescheduling", this)); + RescheduleUntailTimer(now); + } +} + +void RequestContext::RescheduleUntailTimer(TimeStamp const& now) { + MOZ_ASSERT(mUntailAt >= now); + + if (mUntailTimer) { + mUntailTimer->Cancel(); + } + + if (!mTailQueue.Length()) { + mUntailTimer = nullptr; + mTimerScheduledAt = TimeStamp(); + return; + } + + TimeDuration interval = mUntailAt - now; + if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) { + // When the number of untailed requests goes down, + // let's half the interval, since it's likely we would + // reschedule for a shorter time again very soon. + // This will likely save rescheduling this timer. + interval = interval / int64_t(2); + mTimerScheduledAt = mUntailAt - interval; + } else { + mTimerScheduledAt = mUntailAt; + } + + uint32_t delay = interval.ToMilliseconds(); + nsresult rv = + NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay, + nsITimer::TYPE_ONE_SHOT, nullptr); + if (NS_FAILED(rv)) { + NS_WARNING("Could not reschedule untail timer"); + } + + LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay)); +} + +NS_IMETHODIMP +RequestContext::Notify(nsITimer* timer) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(timer == mUntailTimer); + MOZ_ASSERT(!mTimerScheduledAt.IsNull()); + MOZ_ASSERT(mTailQueue.Length()); + + mUntailTimer = nullptr; + + TimeStamp now = TimeStamp::NowLoRes(); + if (mUntailAt > mTimerScheduledAt && mUntailAt > now) { + LOG(("RequestContext %p timer fired too soon, rescheduling", this)); + RescheduleUntailTimer(now); + return NS_OK; + } + + // Must drop to allow re-engage of the timer + mTimerScheduledAt = TimeStamp(); + + ProcessTailQueue(NS_OK); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetName(nsACString& aName) { + aName.AssignLiteral("RequestContext"); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest, + bool* aBlocked) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu", + this, aRequest, mTailQueue.Length())); + + *aBlocked = false; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + if (mUntailAt.IsNull()) { + LOG((" untail time passed")); + return NS_OK; + } + + if (mAfterDOMContentLoaded && !mNonTailRequests) { + LOG((" after DOMContentLoaded and no untailed requests")); + return NS_OK; + } + + if (!gHttpHandler) { + // Xpcshell tests may not have http handler + LOG((" missing gHttpHandler?")); + return NS_OK; + } + + *aBlocked = true; + mTailQueue.AppendElement(aRequest); + + LOG((" request queued")); + + if (!mUntailTimer) { + ScheduleUnblock(); + } + + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) { + MOZ_ASSERT(NS_IsMainThread()); + + bool removed = mTailQueue.RemoveElement(aRequest); + + LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this, + aRequest, removed)); + + // Stop untail timer if all tail requests are canceled. + if (removed && mTailQueue.IsEmpty()) { + if (mUntailTimer) { + mUntailTimer->Cancel(); + mUntailTimer = nullptr; + } + + // Must drop to allow re-engage of the timer + mTimerScheduledAt = TimeStamp(); + } + + return NS_OK; +} + +void RequestContext::ProcessTailQueue(nsresult aResult) { + LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32, + this, mTailQueue.Length(), static_cast(aResult))); + + if (mUntailTimer) { + mUntailTimer->Cancel(); + mUntailTimer = nullptr; + } + + // Must drop to stop tailing requests + mUntailAt = TimeStamp(); + + nsTArray queue = std::move(mTailQueue); + + for (const auto& request : queue) { + LOG((" untailing %p", request.get())); + request->OnTailUnblock(aResult); + } +} + +NS_IMETHODIMP +RequestContext::CancelTailPendingRequests(nsresult aResult) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(NS_FAILED(aResult)); + + ProcessTailQueue(aResult); + return NS_OK; +} + +// nsIRequestContextService +RequestContextService* RequestContextService::sSelf = nullptr; + +NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver) + +RequestContextService::RequestContextService() { + MOZ_ASSERT(!sSelf, "multiple rcs instances!"); + MOZ_ASSERT(NS_IsMainThread()); + sSelf = this; + + nsCOMPtr runtime = do_GetService("@mozilla.org/xre/runtime;1"); + runtime->GetProcessID(&mRCIDNamespace); +} + +RequestContextService::~RequestContextService() { + MOZ_ASSERT(NS_IsMainThread()); + Shutdown(); + sSelf = nullptr; +} + +nsresult RequestContextService::Init() { + nsresult rv; + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_FAILED(rv)) { + return rv; + } + obs->AddObserver(this, "content-document-interactive", false); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +void RequestContextService::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + // We need to do this to prevent the requests from being scheduled after + // shutdown. + for (const auto& data : mTable.Values()) { + data->CancelTailPendingRequests(NS_ERROR_ABORT); + } + mTable.Clear(); + sShutdown = true; +} + +/* static */ +already_AddRefed +RequestContextService::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sShutdown) { + return nullptr; + } + + RefPtr svc; + if (gSingleton) { + svc = gSingleton; + } else { + svc = new RequestContextService(); + nsresult rv = svc->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); + gSingleton = svc; + ClearOnShutdown(&gSingleton); + } + + return svc.forget(); +} + +NS_IMETHODIMP +RequestContextService::GetRequestContext(const uint64_t rcID, + nsIRequestContext** rc) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + if (!rcID) { + return NS_ERROR_INVALID_ARG; + } + + *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] { + nsCOMPtr newSC = new RequestContext(rcID); + return newSC; + })).take(); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup, + nsIRequestContext** rc) { + nsresult rv; + + uint64_t rcID; + rv = aLoadGroup->GetRequestContextID(&rcID); + if (NS_FAILED(rv)) { + return rv; + } + + return GetRequestContext(rcID, rc); +} + +NS_IMETHODIMP +RequestContextService::NewRequestContext(nsIRequestContext** rc) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + uint64_t rcID = + ((static_cast(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) | + mNextRCID++; + + nsCOMPtr newSC = new RequestContext(rcID); + mTable.InsertOrUpdate(rcID, newSC); + newSC.swap(*rc); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::RemoveRequestContext(const uint64_t rcID) { + MOZ_ASSERT(NS_IsMainThread()); + mTable.Remove(rcID); + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::Observe(nsISupports* subject, const char* topic, + const char16_t* data_unicode) { + MOZ_ASSERT(NS_IsMainThread()); + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + return NS_OK; + } + + if (!strcmp("content-document-interactive", topic)) { + nsCOMPtr document(do_QueryInterface(subject)); + MOZ_ASSERT(document); + // We want this be triggered also for iframes, since those track their + // own request context ids. + if (!document) { + return NS_OK; + } + nsIDocShell* ds = document->GetDocShell(); + // XML documents don't always have a docshell assigned + if (!ds) { + return NS_OK; + } + nsCOMPtr dl(do_QueryInterface(ds)); + if (!dl) { + return NS_OK; + } + nsCOMPtr lg; + dl->GetLoadGroup(getter_AddRefs(lg)); + if (!lg) { + return NS_OK; + } + nsCOMPtr rc; + GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc)); + if (rc) { + rc->DOMContentLoaded(); + } + + return NS_OK; + } + + MOZ_ASSERT(false, "Unexpected observer topic"); + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h new file mode 100644 index 0000000000..4aafdb8ed6 --- /dev/null +++ b/netwerk/base/RequestContextService.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla__net__RequestContextService_h +#define mozilla__net__RequestContextService_h + +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsIObserver.h" +#include "nsIRequestContext.h" + +namespace mozilla { +namespace net { + +class RequestContextService final : public nsIRequestContextService, + public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXTSERVICE + NS_DECL_NSIOBSERVER + + static already_AddRefed GetOrCreate(); + + private: + RequestContextService(); + virtual ~RequestContextService(); + + nsresult Init(); + void Shutdown(); + + static RequestContextService* sSelf; + + nsInterfaceHashtable mTable; + uint32_t mRCIDNamespace{0}; + uint32_t mNextRCID{1}; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla__net__RequestContextService_h diff --git a/netwerk/base/SSLTokensCache.cpp b/netwerk/base/SSLTokensCache.cpp new file mode 100644 index 0000000000..34ca2d82c9 --- /dev/null +++ b/netwerk/base/SSLTokensCache.cpp @@ -0,0 +1,570 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SSLTokensCache.h" + +#include "CertVerifier.h" +#include "CommonSocketControl.h" +#include "TransportSecurityInfo.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "nsIOService.h" +#include "ssl.h" +#include "sslexp.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gSSLTokensCacheLog("SSLTokensCache"); +#undef LOG +#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args) +#undef LOG5_ENABLED +#define LOG5_ENABLED() \ + MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose) + +class ExpirationComparator { + public: + bool Equals(SSLTokensCache::TokenCacheRecord* a, + SSLTokensCache::TokenCacheRecord* b) const { + return a->mExpirationTime == b->mExpirationTime; + } + bool LessThan(SSLTokensCache::TokenCacheRecord* a, + SSLTokensCache::TokenCacheRecord* b) const { + return a->mExpirationTime < b->mExpirationTime; + } +}; + +SessionCacheInfo SessionCacheInfo::Clone() const { + SessionCacheInfo result; + result.mEVStatus = mEVStatus; + result.mCertificateTransparencyStatus = mCertificateTransparencyStatus; + result.mServerCertBytes = mServerCertBytes.Clone(); + result.mSucceededCertChainBytes = + mSucceededCertChainBytes + ? Some(TransformIntoNewArray( + *mSucceededCertChainBytes, + [](const auto& element) { return element.Clone(); })) + : Nothing(); + result.mIsBuiltCertChainRootBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot; + result.mOverridableErrorCategory = mOverridableErrorCategory; + result.mFailedCertChainBytes = + mFailedCertChainBytes + ? Some(TransformIntoNewArray( + *mFailedCertChainBytes, + [](const auto& element) { return element.Clone(); })) + : Nothing(); + return result; +} + +StaticRefPtr SSLTokensCache::gInstance; +StaticMutex SSLTokensCache::sLock; +uint64_t SSLTokensCache::sRecordId = 0; + +SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() { + if (!gInstance) { + return; + } + + gInstance->OnRecordDestroyed(this); +} + +uint32_t SSLTokensCache::TokenCacheRecord::Size() const { + uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) + + sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) + + mSessionCacheInfo.mServerCertBytes.Length() + + sizeof(mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot) + + sizeof(mSessionCacheInfo.mOverridableErrorCategory); + if (mSessionCacheInfo.mSucceededCertChainBytes) { + for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) { + size += cert.Length(); + } + } + if (mSessionCacheInfo.mFailedCertChainBytes) { + for (const auto& cert : mSessionCacheInfo.mFailedCertChainBytes.ref()) { + size += cert.Length(); + } + } + return size; +} + +void SSLTokensCache::TokenCacheRecord::Reset() { + mToken.Clear(); + mExpirationTime = 0; + mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV; + mSessionCacheInfo.mCertificateTransparencyStatus = + nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE; + mSessionCacheInfo.mServerCertBytes.Clear(); + mSessionCacheInfo.mSucceededCertChainBytes.reset(); + mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot.reset(); + mSessionCacheInfo.mOverridableErrorCategory = + nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET; + mSessionCacheInfo.mFailedCertChainBytes.reset(); +} + +uint32_t SSLTokensCache::TokenCacheEntry::Size() const { + uint32_t size = 0; + for (const auto& rec : mRecords) { + size += rec->Size(); + } + return size; +} + +void SSLTokensCache::TokenCacheEntry::AddRecord( + UniquePtr&& aRecord, + nsTArray& aExpirationArray) { + if (mRecords.Length() == + StaticPrefs::network_ssl_tokens_cache_records_per_entry()) { + aExpirationArray.RemoveElement(mRecords[0].get()); + mRecords.RemoveElementAt(0); + } + + aExpirationArray.AppendElement(aRecord.get()); + for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { + if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) { + mRecords.InsertElementAt(i + 1, std::move(aRecord)); + return; + } + } + mRecords.InsertElementAt(0, std::move(aRecord)); +} + +UniquePtr +SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) { + for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { + if (mRecords[i]->mId == aId) { + UniquePtr record = std::move(mRecords[i]); + mRecords.RemoveElementAt(i); + return record; + } + } + return nullptr; +} + +const UniquePtr& +SSLTokensCache::TokenCacheEntry::Get() { + return mRecords[0]; +} + +NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter) + +// static +nsresult SSLTokensCache::Init() { + StaticMutexAutoLock lock(sLock); + + // SSLTokensCache should be only used in parent process and socket process. + // Ideally, parent process should not use this when socket process is enabled. + // However, some xpcsehll tests may need to create and use sockets directly, + // so we still allow to use this in parent process no matter socket process is + // enabled or not. + if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) { + return NS_OK; + } + + MOZ_ASSERT(!gInstance); + + gInstance = new SSLTokensCache(); + + RegisterWeakMemoryReporter(gInstance); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::Shutdown() { + StaticMutexAutoLock lock(sLock); + + if (!gInstance) { + return NS_ERROR_UNEXPECTED; + } + + UnregisterWeakMemoryReporter(gInstance); + + gInstance = nullptr; + + return NS_OK; +} + +SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); } + +SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); } + +// static +nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, + CommonSocketControl* aSocketControl) { + PRUint32 expirationTime; + SSLResumptionTokenInfo tokenInfo; + if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo, + sizeof(tokenInfo)) != SECSuccess) { + LOG((" cannot get expiration time from the token, NSS error %d", + PORT_GetError())); + return NS_ERROR_FAILURE; + } + + expirationTime = tokenInfo.expirationTime; + SSL_DestroyResumptionTokenInfo(&tokenInfo); + + return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime); +} + +// static +nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, + CommonSocketControl* aSocketControl, + PRUint32 aExpirationTime) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]", + PromiseFlatCString(aKey).get(), aTokenLen)); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + if (!aSocketControl) { + return NS_ERROR_FAILURE; + } + nsCOMPtr securityInfo; + nsresult rv = aSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr cert; + securityInfo->GetServerCert(getter_AddRefs(cert)); + if (!cert) { + return NS_ERROR_FAILURE; + } + + nsTArray certBytes; + rv = cert->GetRawDER(certBytes); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe>> succeededCertChainBytes; + nsTArray> succeededCertArray; + rv = securityInfo->GetSucceededCertChain(succeededCertArray); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe isBuiltCertChainRootBuiltInRoot; + if (!succeededCertArray.IsEmpty()) { + succeededCertChainBytes.emplace(); + for (const auto& cert : succeededCertArray) { + nsTArray rawCert; + nsresult rv = cert->GetRawDER(rawCert); + if (NS_FAILED(rv)) { + return rv; + } + succeededCertChainBytes->AppendElement(std::move(rawCert)); + } + + bool builtInRoot = false; + rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot); + if (NS_FAILED(rv)) { + return rv; + } + isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot); + } + + bool isEV; + rv = securityInfo->GetIsExtendedValidation(&isEV); + if (NS_FAILED(rv)) { + return rv; + } + + uint16_t certificateTransparencyStatus; + rv = securityInfo->GetCertificateTransparencyStatus( + &certificateTransparencyStatus); + if (NS_FAILED(rv)) { + return rv; + } + + nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory; + rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe>> failedCertChainBytes; + nsTArray> failedCertArray; + rv = securityInfo->GetFailedCertChain(failedCertArray); + if (NS_FAILED(rv)) { + return rv; + } + if (!failedCertArray.IsEmpty()) { + failedCertChainBytes.emplace(); + for (const auto& cert : failedCertArray) { + nsTArray rawCert; + nsresult rv = cert->GetRawDER(rawCert); + if (NS_FAILED(rv)) { + return rv; + } + failedCertChainBytes->AppendElement(std::move(rawCert)); + } + } + + auto makeUniqueRecord = [&]() { + auto rec = MakeUnique(); + rec->mKey = aKey; + rec->mExpirationTime = aExpirationTime; + MOZ_ASSERT(rec->mToken.IsEmpty()); + rec->mToken.AppendElements(aToken, aTokenLen); + rec->mId = ++sRecordId; + rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes); + rec->mSessionCacheInfo.mSucceededCertChainBytes = + std::move(succeededCertChainBytes); + if (isEV) { + rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV; + } + rec->mSessionCacheInfo.mCertificateTransparencyStatus = + certificateTransparencyStatus; + rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot = + std::move(isBuiltCertChainRootBuiltInRoot); + rec->mSessionCacheInfo.mOverridableErrorCategory = overridableErrorCategory; + rec->mSessionCacheInfo.mFailedCertChainBytes = + std::move(failedCertChainBytes); + return rec; + }; + + TokenCacheEntry* const cacheEntry = + gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) { + if (!entry) { + auto rec = makeUniqueRecord(); + auto cacheEntry = MakeUnique(); + cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray); + entry.Insert(std::move(cacheEntry)); + } else { + // To make sure the cache size is synced, we take away the size of + // whole entry and add it back later. + gInstance->mCacheSize -= entry.Data()->Size(); + entry.Data()->AddRecord(makeUniqueRecord(), + gInstance->mExpirationArray); + } + + return entry->get(); + }); + + gInstance->mCacheSize += cacheEntry->Size(); + + gInstance->LogStats(); + + gInstance->EvictIfNecessary(); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->GetLocked(aKey, aToken, aResult, aTokenId); +} + +nsresult SSLTokensCache::GetLocked(const nsACString& aKey, + nsTArray& aToken, + SessionCacheInfo& aResult, + uint64_t* aTokenId) { + sLock.AssertCurrentThreadOwns(); + + TokenCacheEntry* cacheEntry = nullptr; + + if (mTokenCacheRecords.Get(aKey, &cacheEntry)) { + if (cacheEntry->RecordCount() == 0) { + MOZ_ASSERT(false, "Found a cacheEntry with no records"); + mTokenCacheRecords.Remove(aKey); + return NS_ERROR_NOT_AVAILABLE; + } + + const UniquePtr& rec = cacheEntry->Get(); + aToken = rec->mToken.Clone(); + aResult = rec->mSessionCacheInfo.Clone(); + if (aTokenId) { + *aTokenId = rec->mId; + } + if (StaticPrefs::network_ssl_tokens_cache_use_only_once()) { + mCacheSize -= rec->Size(); + cacheEntry->RemoveWithId(rec->mId); + if (cacheEntry->RecordCount() == 0) { + mTokenCacheRecords.Remove(aKey); + } + } + return NS_OK; + } + + LOG((" token not found")); + return NS_ERROR_NOT_AVAILABLE; +} + +// static +nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->RemoveLocked(aKey, aId); +} + +nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) { + sLock.AssertCurrentThreadOwns(); + + LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]", + PromiseFlatCString(aKey).get(), aId)); + + TokenCacheEntry* cacheEntry; + if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) { + return NS_ERROR_NOT_AVAILABLE; + } + + UniquePtr rec = cacheEntry->RemoveWithId(aId); + if (!rec) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCacheSize -= rec->Size(); + if (cacheEntry->RecordCount() == 0) { + mTokenCacheRecords.Remove(aKey); + } + + // Release the record immediately, so mExpirationArray can be also updated. + rec = nullptr; + + LogStats(); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->RemovAllLocked(aKey); +} + +nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) { + sLock.AssertCurrentThreadOwns(); + + LOG(("SSLTokensCache::RemovAllLocked [key=%s]", + PromiseFlatCString(aKey).get())); + + UniquePtr cacheEntry; + if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCacheSize -= cacheEntry->Size(); + cacheEntry = nullptr; + + LogStats(); + + return NS_OK; +} + +void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) { + mExpirationArray.RemoveElement(aRec); +} + +void SSLTokensCache::EvictIfNecessary() { + // kilobytes to bytes + uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10; + if (mCacheSize <= capacity) { + return; + } + + LOG(("SSLTokensCache::EvictIfNecessary - evicting")); + + mExpirationArray.Sort(ExpirationComparator()); + + while (mCacheSize > capacity && mExpirationArray.Length() > 0) { + DebugOnly rv = + RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "mExpirationArray and mTokenCacheRecords are out of sync!"); + } +} + +void SSLTokensCache::LogStats() { + if (!LOG5_ENABLED()) { + return; + } + LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]", + mExpirationArray.Length(), mCacheSize)); + for (const auto& ent : mTokenCacheRecords.Values()) { + const UniquePtr& rec = ent->Get(); + LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount())); + } +} + +size_t SSLTokensCache::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + + n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf); + + for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) { + n += mallocSizeOf(mExpirationArray[i]); + n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); + n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf); + } + + return n; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf) + +NS_IMETHODIMP +SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + StaticMutexAutoLock lock(sLock); + + MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP, + UNITS_BYTES, + SizeOfIncludingThis(SSLTokensCacheMallocSizeOf), + "Memory used for the SSL tokens cache."); + + return NS_OK; +} + +// static +void SSLTokensCache::Clear() { + LOG(("SSLTokensCache::Clear")); + + StaticMutexAutoLock lock(sLock); + if (!gInstance) { + LOG((" service not initialized")); + return; + } + + gInstance->mExpirationArray.Clear(); + gInstance->mTokenCacheRecords.Clear(); + gInstance->mCacheSize = 0; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SSLTokensCache.h b/netwerk/base/SSLTokensCache.h new file mode 100644 index 0000000000..b335457526 --- /dev/null +++ b/netwerk/base/SSLTokensCache.h @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SSLTokensCache_h_ +#define SSLTokensCache_h_ + +#include "CertVerifier.h" // For EVStatus +#include "mozilla/Maybe.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPtr.h" +#include "nsClassHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsITransportSecurityInfo.h" +#include "nsTArray.h" +#include "nsTHashMap.h" +#include "nsXULAppAPI.h" + +class CommonSocketControl; + +namespace mozilla { +namespace net { + +struct SessionCacheInfo { + SessionCacheInfo Clone() const; + + psm::EVStatus mEVStatus = psm::EVStatus::NotEV; + uint16_t mCertificateTransparencyStatus = + nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE; + nsTArray mServerCertBytes; + Maybe>> mSucceededCertChainBytes; + Maybe mIsBuiltCertChainRootBuiltInRoot; + nsITransportSecurityInfo::OverridableErrorCategory mOverridableErrorCategory; + Maybe>> mFailedCertChainBytes; +}; + +class SSLTokensCache : public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + friend class ExpirationComparator; + + static nsresult Init(); + static nsresult Shutdown(); + + static nsresult Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, CommonSocketControl* aSocketControl); + static nsresult Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, CommonSocketControl* aSocketControl, + PRUint32 aExpirationTime); + static nsresult Get(const nsACString& aKey, nsTArray& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId = nullptr); + static nsresult Remove(const nsACString& aKey, uint64_t aId); + static nsresult RemoveAll(const nsACString& aKey); + static void Clear(); + + private: + SSLTokensCache(); + virtual ~SSLTokensCache(); + + nsresult RemoveLocked(const nsACString& aKey, uint64_t aId); + nsresult RemovAllLocked(const nsACString& aKey); + nsresult GetLocked(const nsACString& aKey, nsTArray& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId); + + void EvictIfNecessary(); + void LogStats(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + static mozilla::StaticRefPtr gInstance; + static StaticMutex sLock MOZ_UNANNOTATED; + static uint64_t sRecordId; + + uint32_t mCacheSize{0}; // Actual cache size in bytes + + class TokenCacheRecord { + public: + ~TokenCacheRecord(); + + uint32_t Size() const; + void Reset(); + + nsCString mKey; + PRUint32 mExpirationTime = 0; + nsTArray mToken; + SessionCacheInfo mSessionCacheInfo; + // An unique id to identify the record. Mostly used when we want to remove a + // record from TokenCacheEntry. + uint64_t mId = 0; + }; + + class TokenCacheEntry { + public: + uint32_t Size() const; + // Add a record into |mRecords|. To make sure |mRecords| is sorted, we + // iterate |mRecords| everytime to find a right place to insert the new + // record. + void AddRecord(UniquePtr&& aRecord, + nsTArray& aExpirationArray); + // This function returns the first record in |mRecords|. + const UniquePtr& Get(); + UniquePtr RemoveWithId(uint64_t aId); + uint32_t RecordCount() const { return mRecords.Length(); } + const nsTArray>& Records() { return mRecords; } + + private: + // The records in this array are ordered by the expiration time. + nsTArray> mRecords; + }; + + void OnRecordDestroyed(TokenCacheRecord* aRec); + + nsClassHashtable mTokenCacheRecords; + nsTArray mExpirationArray; +}; + +} // namespace net +} // namespace mozilla + +#endif // SSLTokensCache_h_ diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp new file mode 100644 index 0000000000..db62ea691d --- /dev/null +++ b/netwerk/base/ShutdownLayer.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "ShutdownLayer.h" +#include "prerror.h" +#include "private/pprio.h" +#include "prmem.h" +#include + +static PRDescIdentity sWinSockShutdownLayerIdentity; +static PRIOMethods sWinSockShutdownLayerMethods; +static PRIOMethods* sWinSockShutdownLayerMethodsPtr = nullptr; + +namespace mozilla { +namespace net { + +extern PRDescIdentity nsNamedPipeLayerIdentity; + +} // namespace net +} // namespace mozilla + +PRStatus WinSockClose(PRFileDesc* aFd) { + MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity, + "Windows shutdown layer not on the top of the stack"); + + PROsfd osfd = PR_FileDesc2NativeHandle(aFd); + if (osfd != -1) { + shutdown(osfd, SD_BOTH); + } + + aFd->identity = PR_INVALID_IO_LAYER; + + if (aFd->lower) { + return aFd->lower->methods->close(aFd->lower); + } else { + return PR_SUCCESS; + } +} + +nsresult mozilla::net::AttachShutdownLayer(PRFileDesc* aFd) { + if (!sWinSockShutdownLayerMethodsPtr) { + sWinSockShutdownLayerIdentity = + PR_GetUniqueIdentity("windows shutdown call layer"); + sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods(); + sWinSockShutdownLayerMethods.close = WinSockClose; + sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods; + } + + if (mozilla::net::nsNamedPipeLayerIdentity && + PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) { + // Do not attach shutdown layer on named pipe layer, + // it is for PR_NSPR_IO_LAYER only. + return NS_OK; + } + + PRFileDesc* layer; + PRStatus status; + + layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity, + sWinSockShutdownLayerMethodsPtr); + if (!layer) { + return NS_OK; + } + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h new file mode 100644 index 0000000000..2bf68fdcdc --- /dev/null +++ b/netwerk/base/ShutdownLayer.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SHUTDOWNLAYER_H___ +#define SHUTDOWNLAYER_H___ + +#include "nscore.h" +#include "prio.h" + +namespace mozilla { +namespace net { + +// This is only for windows. This layer will be attached jus before PR_CLose +// is call and it will only call shutdown(sock). +extern nsresult AttachShutdownLayer(PRFileDesc* fd); + +} // namespace net +} // namespace mozilla + +#endif /* SHUTDOWN_H___ */ diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp new file mode 100644 index 0000000000..46b8654efa --- /dev/null +++ b/netwerk/base/SimpleBuffer.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleBuffer.h" +#include + +namespace mozilla { +namespace net { + +nsresult SimpleBuffer::Write(char* src, size_t len) { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + while (len > 0) { + SimpleBufferPage* p = mBufferList.getLast(); + if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) { + // no room.. make a new page + p = nullptr; + } + if (!p) { + p = new (fallible) SimpleBufferPage(); + if (!p) { + mStatus = NS_ERROR_OUT_OF_MEMORY; + return mStatus; + } + mBufferList.insertBack(p); + } + size_t roomOnPage = + SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset; + size_t toWrite = std::min(roomOnPage, len); + memcpy(p->mBuffer + p->mWriteOffset, src, toWrite); + src += toWrite; + len -= toWrite; + p->mWriteOffset += toWrite; + mAvailable += toWrite; + } + return NS_OK; +} + +size_t SimpleBuffer::Read(char* dest, size_t maxLen) { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + if (NS_FAILED(mStatus)) { + return 0; + } + + size_t rv = 0; + for (SimpleBufferPage* p = mBufferList.getFirst(); p && (rv < maxLen); + p = mBufferList.getFirst()) { + size_t avail = p->mWriteOffset - p->mReadOffset; + size_t toRead = std::min(avail, (maxLen - rv)); + memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead); + rv += toRead; + p->mReadOffset += toRead; + if (p->mReadOffset == p->mWriteOffset) { + p->remove(); + delete p; + } + } + + MOZ_ASSERT(mAvailable >= rv); + mAvailable -= rv; + return rv; +} + +size_t SimpleBuffer::Available() { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + return NS_SUCCEEDED(mStatus) ? mAvailable : 0; +} + +void SimpleBuffer::Clear() { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + SimpleBufferPage* p; + while ((p = mBufferList.popFirst())) { + delete p; + } + mAvailable = 0; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h new file mode 100644 index 0000000000..031f59afe5 --- /dev/null +++ b/netwerk/base/SimpleBuffer.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SimpleBuffer_h__ +#define SimpleBuffer_h__ + +/* + This class is similar to a nsPipe except it does not have any locking, stores + an unbounded amount of data, can only be used on one thread, and has much + simpler result code semantics to deal with. +*/ + +#include "prtypes.h" +#include "ErrorList.h" +#include "mozilla/LinkedList.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace net { + +class SimpleBufferPage : public LinkedListElement { + public: + SimpleBufferPage() = default; + static const size_t kSimpleBufferPageSize = 32000; + + private: + friend class SimpleBuffer; + char mBuffer[kSimpleBufferPageSize]{0}; + size_t mReadOffset{0}; + size_t mWriteOffset{0}; +}; + +class SimpleBuffer { + public: + SimpleBuffer() = default; + ~SimpleBuffer() = default; + + nsresult Write(char* src, size_t len); // return OK or OUT_OF_MEMORY + size_t Read(char* dest, size_t maxLen); // return bytes read + size_t Available(); + void Clear(); + + private: + NS_DECL_OWNINGTHREAD + + nsresult mStatus{NS_OK}; + AutoCleanLinkedList mBufferList; + size_t mAvailable{0}; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/SimpleChannel.cpp b/netwerk/base/SimpleChannel.cpp new file mode 100644 index 0000000000..07d54c5f4e --- /dev/null +++ b/netwerk/base/SimpleChannel.cpp @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleChannel.h" + +#include "nsBaseChannel.h" +#include "nsIChannel.h" +#include "nsIChildChannel.h" +#include "nsICancelable.h" +#include "nsIInputStream.h" +#include "nsIRequest.h" +#include "nsISupportsImpl.h" +#include "nsNetUtil.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/PSimpleChannelChild.h" + +namespace mozilla { +namespace net { + +SimpleChannel::SimpleChannel(UniquePtr&& aCallbacks) + : mCallbacks(std::move(aCallbacks)) { + EnableSynthesizedProgressEvents(true); +} + +nsresult SimpleChannel::OpenContentStream(bool async, + nsIInputStream** streamOut, + nsIChannel** channel) { + NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED); + + nsCOMPtr stream; + MOZ_TRY_VAR(stream, mCallbacks->OpenContentStream(async, this)); + MOZ_ASSERT(stream); + + mCallbacks = nullptr; + + *channel = nullptr; + stream.forget(streamOut); + return NS_OK; +} + +nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) { + NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED); + + RequestOrReason res = mCallbacks->StartAsyncRead(listener, this); + + if (res.isErr()) { + return res.propagateErr(); + } + + mCallbacks = nullptr; + + RequestOrCancelable value = res.unwrap(); + + if (value.is()) { + nsCOMPtr req = value.as().get(); + req.forget(request); + } else if (value.is()) { + nsCOMPtr cancelable = value.as().get(); + cancelable.forget(cancelableRequest); + } else { + MOZ_ASSERT_UNREACHABLE( + "StartAsyncRead didn't return a NotNull nsIRequest or nsICancelable."); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(SimpleChannelChild, SimpleChannel, nsIChildChannel) + +SimpleChannelChild::SimpleChannelChild( + UniquePtr&& aCallbacks) + : SimpleChannel(std::move(aCallbacks)) {} + +NS_IMETHODIMP +SimpleChannelChild::ConnectParent(uint32_t aId) { + MOZ_ASSERT(NS_IsMainThread()); + mozilla::dom::ContentChild* cc = + static_cast(gNeckoChild->Manager()); + if (cc->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + // Reference freed in DeallocPSimpleChannelChild. + if (!gNeckoChild->SendPSimpleChannelConstructor(do_AddRef(this).take(), + aId)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { + if (CanSend()) { + MOZ_ASSERT(NS_IsMainThread()); + } + + nsresult rv; + rv = AsyncOpen(aListener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (CanSend()) { + Unused << Send__delete__(this); + } + return NS_OK; +} + +already_AddRefed NS_NewSimpleChannelInternal( + nsIURI* aURI, nsILoadInfo* aLoadInfo, + UniquePtr&& aCallbacks) { + RefPtr chan; + if (IsNeckoChild()) { + chan = new SimpleChannelChild(std::move(aCallbacks)); + } else { + chan = new SimpleChannel(std::move(aCallbacks)); + } + + chan->SetURI(aURI); + + MOZ_ALWAYS_SUCCEEDS(chan->SetLoadInfo(aLoadInfo)); + + return chan.forget(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleChannel.h b/netwerk/base/SimpleChannel.h new file mode 100644 index 0000000000..d469adf65d --- /dev/null +++ b/netwerk/base/SimpleChannel.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SimpleChannel_h +#define SimpleChannel_h + +#include "mozilla/ResultExtensions.h" +#include "mozilla/UniquePtr.h" +#include "nsBaseChannel.h" +#include "nsIChildChannel.h" +#include "mozilla/net/PSimpleChannelChild.h" +#include "nsCOMPtr.h" + +class nsIChannel; +class nsIInputStream; +class nsILoadInfo; +class nsIRequest; +class nsIStreamListener; +class nsIURI; + +//----------------------------------------------------------------------------- + +namespace mozilla { + +using InputStreamOrReason = Result, nsresult>; +using NotNullRequest = NotNull>; +using NotNullCancelable = NotNull>; +using RequestOrCancelable = Variant; +using RequestOrReason = Result; + +namespace net { + +class SimpleChannelCallbacks { + public: + virtual InputStreamOrReason OpenContentStream(bool async, + nsIChannel* channel) = 0; + + virtual RequestOrReason StartAsyncRead(nsIStreamListener* stream, + nsIChannel* channel) = 0; + + virtual ~SimpleChannelCallbacks() = default; +}; + +template +class SimpleChannelCallbacksImpl final : public SimpleChannelCallbacks { + public: + SimpleChannelCallbacksImpl(F1&& aStartAsyncRead, F2&& aOpenContentStream, + T* context) + : mStartAsyncRead(aStartAsyncRead), + mOpenContentStream(aOpenContentStream), + mContext(context) {} + + virtual ~SimpleChannelCallbacksImpl() = default; + + virtual InputStreamOrReason OpenContentStream(bool async, + nsIChannel* channel) override { + return mOpenContentStream(async, channel, mContext); + } + + virtual RequestOrReason StartAsyncRead(nsIStreamListener* listener, + nsIChannel* channel) override { + return mStartAsyncRead(listener, channel, mContext); + } + + private: + F1 mStartAsyncRead; + F2 mOpenContentStream; + RefPtr mContext; +}; + +class SimpleChannel : public nsBaseChannel { + public: + explicit SimpleChannel(UniquePtr&& aCallbacks); + + protected: + virtual ~SimpleChannel() = default; + + virtual nsresult OpenContentStream(bool async, nsIInputStream** streamOut, + nsIChannel** channel) override; + + virtual nsresult BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) override; + + private: + UniquePtr mCallbacks; +}; + +class SimpleChannelChild final : public SimpleChannel, + public nsIChildChannel, + public PSimpleChannelChild { + public: + explicit SimpleChannelChild(UniquePtr&& aCallbacks); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICHILDCHANNEL + + private: + virtual ~SimpleChannelChild() = default; +}; + +already_AddRefed NS_NewSimpleChannelInternal( + nsIURI* aURI, nsILoadInfo* aLoadInfo, + UniquePtr&& aCallbacks); + +} // namespace net +} // namespace mozilla + +/** + * Creates a simple channel which wraps an input stream created by the given + * callbacks. The callbacks are not called until the underlying AsyncOpen or + * Open methods are called, and correspond to the nsBaseChannel::StartAsyncRead + * and nsBaseChannel::OpenContentStream methods of the same names. + * + * The last two arguments of each callback are the created channel instance, + * and the ref-counted context object passed to NS_NewSimpleChannel. A strong + * reference to that object is guaranteed to be kept alive until after a + * callback successfully completes. + */ +template +inline already_AddRefed NS_NewSimpleChannel( + nsIURI* aURI, nsILoadInfo* aLoadInfo, T* context, F1&& aStartAsyncRead, + F2&& aOpenContentStream) { + using namespace mozilla; + + auto callbacks = MakeUnique>( + std::forward(aStartAsyncRead), std::forward(aOpenContentStream), + context); + + return net::NS_NewSimpleChannelInternal(aURI, aLoadInfo, + std::move(callbacks)); +} + +template +inline already_AddRefed NS_NewSimpleChannel(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + T* context, + F1&& aStartAsyncRead) { + using namespace mozilla; + + auto openContentStream = [](bool async, nsIChannel* channel, T* context) { + return Err(NS_ERROR_NOT_IMPLEMENTED); + }; + + return NS_NewSimpleChannel(aURI, aLoadInfo, context, + std::forward(aStartAsyncRead), + std::move(openContentStream)); +} + +#endif // SimpleChannel_h diff --git a/netwerk/base/SimpleChannelParent.cpp b/netwerk/base/SimpleChannelParent.cpp new file mode 100644 index 0000000000..3a58e14843 --- /dev/null +++ b/netwerk/base/SimpleChannelParent.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleChannelParent.h" +#include "mozilla/Assertions.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" + +#ifdef FUZZING_SNAPSHOT +# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__ +#else +# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__) +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(SimpleChannelParent, nsIParentChannel, nsIStreamListener) + +bool SimpleChannelParent::Init(const uint64_t& aChannelId) { + nsCOMPtr channel; + + MOZ_ALWAYS_SUCCEEDS_FUZZING( + NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel))); + + return true; +} + +NS_IMETHODIMP +SimpleChannelParent::SetParentListener(ParentChannelListener* aListener) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aPrefix) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHashes) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::Delete() { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::GetRemoteType(nsACString& aRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void SimpleChannelParent::ActorDestroy(ActorDestroyReason aWhy) {} + +NS_IMETHODIMP +SimpleChannelParent::OnStartRequest(nsIRequest* aRequest) { + // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on + // the created nsSimpleChannel. We don't have anywhere to send the data in the + // parent, so abort the binding. + return NS_BINDING_ABORTED; +} + +NS_IMETHODIMP +SimpleChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + // See above. + MOZ_ASSERT(NS_FAILED(aStatusCode)); + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + // See above. + MOZ_CRASH("Should never be called"); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleChannelParent.h b/netwerk/base/SimpleChannelParent.h new file mode 100644 index 0000000000..6a8f2c867b --- /dev/null +++ b/netwerk/base/SimpleChannelParent.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_SIMPLECHANNELPARENT_H +#define NS_SIMPLECHANNELPARENT_H + +#include "nsIParentChannel.h" +#include "nsISupportsImpl.h" + +#include "mozilla/net/PSimpleChannelParent.h" + +namespace mozilla { +namespace net { + +// In order to support HTTP redirects, we need to implement the HTTP +// redirection API, which requires a class that implements nsIParentChannel +// and which calls NS_LinkRedirectChannels. +class SimpleChannelParent : public nsIParentChannel, + public PSimpleChannelParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER; // semicolon for clang-format bug 1629756 + + [[nodiscard]] bool Init(const uint64_t& aChannelId); + + private: + ~SimpleChannelParent() = default; + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NS_SIMPLECHANNELPARENT_H */ diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp new file mode 100644 index 0000000000..131ea50573 --- /dev/null +++ b/netwerk/base/TLSServerSocket.cpp @@ -0,0 +1,446 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TLSServerSocket.h" + +#include "mozilla/net/DNS.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentSubstring.h" +#include "nsIServerSocket.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsNetCID.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransport2.h" +#include "nsThreadUtils.h" +#include "ScopedNSSTypes.h" +#include "ssl.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// TLSServerSocket +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket, nsServerSocket, nsITLSServerSocket) + +nsresult TLSServerSocket::SetSocketDefaults() { + // Set TLS options on the listening socket + mFD = SSL_ImportFD(nullptr, mFD); + if (NS_WARN_IF(!mFD)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSL_OptionSet(mFD, SSL_SECURITY, true); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true); + SSL_OptionSet(mFD, SSL_NO_CACHE, true); + + // We don't currently notify the server API consumer of renegotiation events + // (to revalidate peer certs, etc.), so disable it for now. + SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER); + + SetSessionTickets(true); + SetRequestClientCertificate(REQUEST_NEVER); + + return NS_OK; +} + +void TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD, + const NetAddr& aClientAddr) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + nsresult rv; + + RefPtr trans = new nsSocketTransport; + if (NS_WARN_IF(!trans)) { + mCondition = NS_ERROR_OUT_OF_MEMORY; + return; + } + + RefPtr info = new TLSServerConnectionInfo(); + info->mServerSocket = this; + info->mTransport = trans; + nsCOMPtr infoInterfaceRequestor(info); + rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, + infoInterfaceRequestor); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCondition = rv; + return; + } + + // Override the default peer certificate validation, so that server consumers + // can make their own choice after the handshake completes. + SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr); + // Once the TLS handshake has completed, the server consumer is notified and + // has access to various TLS state details. + // It's safe to pass info here because the socket transport holds it as + // |mSecInfo| which keeps it alive for the lifetime of the socket. + SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback, + info); + + // Notify the consumer of the new client so it can manage the streams. + // Security details aren't known yet. The security observer will be notified + // later when they are ready. + nsCOMPtr serverSocket = + do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this)); + mListener->OnSocketAccepted(serverSocket, trans); +} + +nsresult TLSServerSocket::OnSocketListen() { + if (NS_WARN_IF(!mServerCert)) { + return NS_ERROR_NOT_INITIALIZED; + } + + UniqueCERTCertificate cert(mServerCert->GetCert()); + if (NS_WARN_IF(!cert)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr)); + if (NS_WARN_IF(!key)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSLKEAType certKEA = NSS_FindCertKEAType(cert.get()); + + nsresult rv = + MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), certKEA)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// static +SECStatus TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, + PRBool isServer) { + // Allow any client cert here, server consumer code can decide whether it's + // okay after being notified of the new client socket. + return SECSuccess; +} + +//----------------------------------------------------------------------------- +// TLSServerSocket::nsITLSServerSocket +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TLSServerSocket::GetServerCert(nsIX509Cert** aCert) { + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = do_AddRef(mServerCert).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetServerCert(nsIX509Cert* aCert) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + mServerCert = aCert; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetSessionTickets(bool aEnabled) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetRequestClientCertificate(uint32_t aMode) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER); + + switch (aMode) { + case REQUEST_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR); + break; + case REQUIRE_FIRST_HANDSHAKE: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE); + break; + case REQUIRE_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS); + break; + default: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER); + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + + SSLVersionRange range = {aMinVersion, aMaxVersion}; + if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// TLSServerConnectionInfo +//----------------------------------------------------------------------------- + +namespace { + +class TLSServerSecurityObserverProxy final + : public nsITLSServerSecurityObserver { + ~TLSServerSecurityObserverProxy() = default; + + public: + explicit TLSServerSecurityObserverProxy( + nsITLSServerSecurityObserver* aListener) + : mListener(new nsMainThreadPtrHolder( + "TLSServerSecurityObserverProxy::mListener", aListener)) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERSECURITYOBSERVER + + class OnHandshakeDoneRunnable : public Runnable { + public: + OnHandshakeDoneRunnable( + const nsMainThreadPtrHandle& aListener, + nsITLSServerSocket* aServer, nsITLSClientStatus* aStatus) + : Runnable( + "net::TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable"), + mListener(aListener), + mServer(aServer), + mStatus(aStatus) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle mListener; + nsCOMPtr mServer; + nsCOMPtr mStatus; + }; + + private: + nsMainThreadPtrHandle mListener; +}; + +NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy, nsITLSServerSecurityObserver) + +NS_IMETHODIMP +TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer, + nsITLSClientStatus* aStatus) { + RefPtr r = + new OnHandshakeDoneRunnable(mListener, aServer, aStatus); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run() { + mListener->OnHandshakeDone(mServer, mStatus); + return NS_OK; +} + +} // namespace + +NS_IMPL_ISUPPORTS(TLSServerConnectionInfo, nsITLSServerConnectionInfo, + nsITLSClientStatus, nsIInterfaceRequestor) + +TLSServerConnectionInfo::~TLSServerConnectionInfo() { + RefPtr observer; + { + MutexAutoLock lock(mLock); + observer = ToRefPtr(std::move(mSecurityObserver)); + } + + if (observer) { + NS_ReleaseOnMainThread("TLSServerConnectionInfo::mSecurityObserver", + observer.forget()); + } +} + +NS_IMETHODIMP +TLSServerConnectionInfo::SetSecurityObserver( + nsITLSServerSecurityObserver* aObserver) { + { + MutexAutoLock lock(mLock); + if (!aObserver) { + mSecurityObserver = nullptr; + return NS_OK; + } + + mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver); + // Call `OnHandshakeDone` if TLS handshake is already completed. + if (mTlsVersionUsed != TLS_VERSION_UNKNOWN) { + nsCOMPtr serverSocket; + GetServerSocket(getter_AddRefs(serverSocket)); + mSecurityObserver->OnHandshakeDone(serverSocket, this); + mSecurityObserver = nullptr; + } + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) { + if (NS_WARN_IF(!aSocket)) { + return NS_ERROR_INVALID_POINTER; + } + *aSocket = do_AddRef(mServerSocket).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) { + if (NS_WARN_IF(!aStatus)) { + return NS_ERROR_INVALID_POINTER; + } + *aStatus = do_AddRef(this).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) { + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = do_AddRef(mPeerCert).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) { + if (NS_WARN_IF(!aTlsVersionUsed)) { + return NS_ERROR_INVALID_POINTER; + } + *aTlsVersionUsed = mTlsVersionUsed; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) { + aCipherName.Assign(mCipherName); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) { + if (NS_WARN_IF(!aKeyLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aKeyLength = mKeyLength; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) { + if (NS_WARN_IF(!aMacLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aMacLength = mMacLength; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsITLSServerConnectionInfo))) { + *aResult = static_cast(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +// static +void TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) { + RefPtr info = + static_cast(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 certDB = + do_GetService(NS_X509CERTDB_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr clientCertPSM; + nsTArray clientCertBytes; + clientCertBytes.AppendElements(clientCert->derCert.data, + clientCert->derCert.len); + rv = certDB->ConstructX509(clientCertBytes, getter_AddRefs(clientCertPSM)); + if (NS_FAILED(rv)) { + return rv; + } + + mPeerCert = clientCertPSM; + } + + SSLChannelInfo channelInfo; + rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo))); + if (NS_FAILED(rv)) { + return rv; + } + + SSLCipherSuiteInfo cipherInfo; + rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, + sizeof(cipherInfo))); + if (NS_FAILED(rv)) { + return rv; + } + mCipherName.Assign(cipherInfo.cipherSuiteName); + mKeyLength = cipherInfo.effectiveKeyBits; + mMacLength = cipherInfo.macBits; + + // Notify consumer code that handshake is complete + nsCOMPtr observer; + { + MutexAutoLock lock(mLock); + mTlsVersionUsed = channelInfo.protocolVersion; + if (!mSecurityObserver) { + return NS_OK; + } + mSecurityObserver.swap(observer); + } + nsCOMPtr serverSocket; + GetServerSocket(getter_AddRefs(serverSocket)); + observer->OnHandshakeDone(serverSocket, this); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h new file mode 100644 index 0000000000..c8f4380d46 --- /dev/null +++ b/netwerk/base/TLSServerSocket.h @@ -0,0 +1,81 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_TLSServerSocket_h +#define mozilla_net_TLSServerSocket_h + +#include "nsIInterfaceRequestor.h" +#include "nsITLSServerSocket.h" +#include "nsServerSocket.h" +#include "nsString.h" +#include "mozilla/Mutex.h" +#include "seccomon.h" + +namespace mozilla { +namespace net { + +class TLSServerSocket final : public nsServerSocket, public nsITLSServerSocket { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSISERVERSOCKET(nsServerSocket::) + NS_DECL_NSITLSSERVERSOCKET + + // Override methods from nsServerSocket + virtual void CreateClientTransport(PRFileDesc* clientFD, + const NetAddr& clientAddr) override; + virtual nsresult SetSocketDefaults() override; + virtual nsresult OnSocketListen() override; + + TLSServerSocket() = default; + + private: + virtual ~TLSServerSocket() = default; + + static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, PRBool isServer); + + nsCOMPtr mServerCert; +}; + +class TLSServerConnectionInfo : public nsITLSServerConnectionInfo, + public nsITLSClientStatus, + public nsIInterfaceRequestor { + friend class TLSServerSocket; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERCONNECTIONINFO + NS_DECL_NSITLSCLIENTSTATUS + NS_DECL_NSIINTERFACEREQUESTOR + + TLSServerConnectionInfo() = default; + + private: + virtual ~TLSServerConnectionInfo(); + + static void HandshakeCallback(PRFileDesc* aFD, void* aArg); + nsresult HandshakeCallback(PRFileDesc* aFD); + + RefPtr mServerSocket; + // Weak ref to the transport, to avoid cycles since the transport holds a + // reference to the TLSServerConnectionInfo object. This is not handed out to + // anyone, and is only used in HandshakeCallback to close the transport in + // case of an error. After this, it's set to nullptr. + nsISocketTransport* mTransport{nullptr}; + nsCOMPtr mPeerCert; + int16_t mTlsVersionUsed{TLS_VERSION_UNKNOWN}; + nsCString mCipherName; + uint32_t mKeyLength{0}; + uint32_t mMacLength{0}; + // lock protects access to mSecurityObserver + mozilla::Mutex mLock{"TLSServerConnectionInfo.mLock"}; + nsCOMPtr mSecurityObserver + MOZ_GUARDED_BY(mLock); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_TLSServerSocket_h diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp new file mode 100644 index 0000000000..e5cbe5e213 --- /dev/null +++ b/netwerk/base/TRRLoadInfo.cpp @@ -0,0 +1,821 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TRRLoadInfo.h" +#include "mozilla/dom/ClientSource.h" +#include "nsContentUtils.h" +#include "nsIRedirectHistoryEntry.h" + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(TRRLoadInfo, nsILoadInfo) + +TRRLoadInfo::TRRLoadInfo(nsIURI* aResultPrincipalURI, + nsContentPolicyType aContentPolicyType) + : mResultPrincipalURI(aResultPrincipalURI), + mInternalContentPolicyType(aContentPolicyType) {} + +already_AddRefed TRRLoadInfo::Clone() const { + nsCOMPtr loadInfo = + new TRRLoadInfo(mResultPrincipalURI, mInternalContentPolicyType); + + return loadInfo.forget(); +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::VirtualGetLoadingPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::TriggeringPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::PrincipalToInherit() { return nullptr; } + +nsIPrincipal* TRRLoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) { + return nullptr; +} + +const nsID& TRRLoadInfo::GetSandboxedNullPrincipalID() { + return mSandboxedNullPrincipalID; +} + +void TRRLoadInfo::ResetSandboxedNullPrincipalID() {} + +nsIPrincipal* TRRLoadInfo::GetTopLevelPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingDocument(Document** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsINode* TRRLoadInfo::LoadingNode() { return nullptr; } + +already_AddRefed TRRLoadInfo::ContextForTopLevelLoad() { + return nullptr; +} + +already_AddRefed TRRLoadInfo::GetLoadingContext() { + return nullptr; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSandboxFlags(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringSandboxFlags(uint32_t aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSecurityMode(uint32_t* aFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsThirdPartyContextToTopWindow( + bool* aIsThirdPartyContextToTopWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsThirdPartyContextToTopWindow( + bool aIsThirdPartyContextToTopWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCookiePolicy(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetStoragePermission( + nsILoadInfo::StoragePermissionState* aHasStoragePermission) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetStoragePermission( + nsILoadInfo::StoragePermissionState aHasStoragePermission) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMetaRefresh(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMetaRefresh(bool aResult) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAboutBlankInherits(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowChrome(bool* aResult) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +TRRLoadInfo::GetDisallowScript(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetDontFollowRedirects(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadErrorPage(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFormSubmission(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsFormSubmission(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSendCSPViolationEvents(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSendCSPViolationEvents(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) { + // We have to use nsContentPolicyType because ExtContentPolicyType is not + // visible from xpidl. + *aResult = static_cast( + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType)); + return NS_OK; +} + +nsContentPolicyType TRRLoadInfo::InternalContentPolicyType() { + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBlockAllMixedContent(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetBrowserDidUpgradeInsecureRequests( + bool aBrowserDidUpgradeInsecureRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowInsecureRedirectToDataURI( + bool aAllowInsecureRedirectToDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowInsecureRedirectToDataURI( + bool* aAllowInsecureRedirectToDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipalDropped(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInnerWindowID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetWorkerAssociatedBrowsingContext( + dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::ResetPrincipalToInheritToNullPrincipal() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetScriptableOriginAttributes( + JSContext* aCx, JS::Handle aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult TRRLoadInfo::GetOriginAttributes( + mozilla::OriginAttributes* aOriginAttributes) { + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult TRRLoadInfo::SetOriginAttributes( + const mozilla::OriginAttributes& aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInitialSecurityCheckDone(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannelToDeriveFrom, + bool aIsInternalRedirect) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetRedirectChainIncludingInternalRedirects( + JSContext* aCx, JS::MutableHandle aChain) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +const nsTArray>& +TRRLoadInfo::RedirectChainIncludingInternalRedirects() { + return mEmptyRedirectChain; +} + +NS_IMETHODIMP +TRRLoadInfo::GetRedirectChain(JSContext* aCx, + JS::MutableHandle aChain) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +const nsTArray>& +TRRLoadInfo::RedirectChain() { + return mEmptyRedirectChain; +} + +const nsTArray>& TRRLoadInfo::AncestorPrincipals() { + return mEmptyPrincipals; +} + +const nsTArray& TRRLoadInfo::AncestorBrowsingContextIDs() { + return mEmptyBrowsingContextIDs; +} + +void TRRLoadInfo::SetCorsPreflightInfo(const nsTArray& aHeaders, + bool aForcePreflight) {} + +const nsTArray& TRRLoadInfo::CorsUnsafeHeaders() { + return mCorsUnsafeHeaders; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForcePreflight(bool* aForcePreflight) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsPreflight(bool* aIsPreflight) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetServiceWorkerTaintingSynthesized( + bool* aServiceWorkerTaintingSynthesized) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTainting(uint32_t* aTaintingOut) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::MaybeIncreaseTainting(uint32_t aTainting) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void TRRLoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {} + +NS_IMETHODIMP +TRRLoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool* aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCspNonce(nsAString& aCspNonce) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCspNonce(const nsAString& aCspNonce) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsTopLevelLoad(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFromProcessingFrameAttributes( + bool* aIsFromProcessingFrameAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMediaRequest(bool aIsMediaRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized( + bool* aShouldSkipCheckForBrokenURLOrZeroSized) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetResultPrincipalURI(nsIURI** aURI) { + nsCOMPtr uri = mResultPrincipalURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::SetResultPrincipalURI(nsIURI* aURI) { + mResultPrincipalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetRequestBlockingReason(uint32_t aReason) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::GetRequestBlockingReason(uint32_t* aReason) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void TRRLoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {} + +const Maybe& TRRLoadInfo::GetClientInfo() { return mClientInfo; } + +void TRRLoadInfo::GiveReservedClientSource( + UniquePtr&& aClientSource) {} + +UniquePtr TRRLoadInfo::TakeReservedClientSource() { + return nullptr; +} + +void TRRLoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {} + +void TRRLoadInfo::OverrideReservedClientInfoInParent( + const ClientInfo& aClientInfo) {} + +const Maybe& TRRLoadInfo::GetReservedClientInfo() { + return mReservedClientInfo; +} + +void TRRLoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {} + +const Maybe& TRRLoadInfo::GetInitialClientInfo() { + return mInitialClientInfo; +} + +void TRRLoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) { +} + +void TRRLoadInfo::ClearController() {} + +const Maybe& TRRLoadInfo::GetController() { + return mController; +} + +void TRRLoadInfo::SetPerformanceStorage( + PerformanceStorage* aPerformanceStorage) {} + +PerformanceStorage* TRRLoadInfo::GetPerformanceStorage() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +already_AddRefed TRRLoadInfo::GetCsp() { + return nullptr; +} + +already_AddRefed TRRLoadInfo::GetPreloadCsp() { + return nullptr; +} + +already_AddRefed TRRLoadInfo::GetCspToInherit() { + return nullptr; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHstsStatus(bool* aHstsStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHstsStatus(bool aHstsStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHasValidUserGestureActivation( + bool* aHasValidUserGestureActivation) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHasValidUserGestureActivation( + bool aHasValidUserGestureActivation) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) { + *aResult = mInternalContentPolicyType; + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowDeprecatedSystemRequests( + bool* aAllowDeprecatedSystemRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowDeprecatedSystemRequests( + bool aAllowDeprecatedSystemRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetParserCreatedScript(bool aParserCreatedScript) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetUnstrippedURI(nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetUnstrippedURI(nsIURI* aURI) { return NS_ERROR_NOT_IMPLEMENTED; } + +nsIInterceptionInfo* TRRLoadInfo::InterceptionInfo() { return nullptr; } +void TRRLoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aPrincipla) {} + +NS_IMETHODIMP +TRRLoadInfo::GetHasInjectedCookieForCookieBannerHandling( + bool* aHasInjectedCookieForCookieBannerHandling) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling( + bool aHasInjectedCookieForCookieBannerHandling) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/TRRLoadInfo.h b/netwerk/base/TRRLoadInfo.h new file mode 100644 index 0000000000..a19c86601e --- /dev/null +++ b/netwerk/base/TRRLoadInfo.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TRRLoadInfo_h +#define mozilla_TRRLoadInfo_h + +#include "nsILoadInfo.h" +#include "nsIURI.h" +#include "nsTArray.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" +#include "mozilla/OriginAttributes.h" + +namespace mozilla { +namespace net { + +// TRRLoadInfo is designed to be used by TRRServiceChannel only. Most of +// nsILoadInfo functions are not implemented since TRRLoadInfo needs to +// support off main thread. +class TRRLoadInfo final : public nsILoadInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADINFO + + TRRLoadInfo(nsIURI* aResultPrincipalURI, + nsContentPolicyType aContentPolicyType); + + already_AddRefed Clone() const; + + private: + virtual ~TRRLoadInfo() = default; + + nsCOMPtr mResultPrincipalURI; + nsContentPolicyType mInternalContentPolicyType; + OriginAttributes mOriginAttributes; + nsTArray> mEmptyRedirectChain; + nsTArray> mEmptyPrincipals; + nsTArray mEmptyBrowsingContextIDs; + nsTArray mCorsUnsafeHeaders; + nsID mSandboxedNullPrincipalID; + Maybe mClientInfo; + Maybe mReservedClientInfo; + Maybe mInitialClientInfo; + Maybe mController; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_TRRLoadInfo_h diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp new file mode 100644 index 0000000000..4313a6ecb3 --- /dev/null +++ b/netwerk/base/ThrottleQueue.cpp @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ThrottleQueue.h" +#include "mozilla/net/InputChannelThrottleQueueParent.h" +#include "nsISeekableStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIOService.h" +#include "nsSocketTransportService2.h" +#include "nsStreamUtils.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- + +class ThrottleInputStream final : public nsIAsyncInputStream, + public nsISeekableStream { + public: + ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + void AllowInput(); + + private: + ~ThrottleInputStream(); + + nsCOMPtr mStream; + RefPtr mQueue; + nsresult mClosedStatus; + + nsCOMPtr mCallback; + nsCOMPtr mEventTarget; +}; + +NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, + nsITellableStream, nsISeekableStream) + +ThrottleInputStream::ThrottleInputStream(nsIInputStream* aStream, + ThrottleQueue* aQueue) + : mStream(aStream), mQueue(aQueue), mClosedStatus(NS_OK) { + MOZ_ASSERT(aQueue != nullptr); +} + +ThrottleInputStream::~ThrottleInputStream() { Close(); } + +NS_IMETHODIMP +ThrottleInputStream::Close() { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + if (mQueue) { + mQueue->DequeueStream(this); + mQueue = nullptr; + mClosedStatus = NS_BASE_STREAM_CLOSED; + } + return mStream->Close(); +} + +NS_IMETHODIMP +ThrottleInputStream::Available(uint64_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + return mStream->Available(aResult); +} + +NS_IMETHODIMP +ThrottleInputStream::StreamStatus() { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + return mStream->StreamStatus(); +} + +NS_IMETHODIMP +ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->Read(aBuf, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(realCount <= aCount); + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + nsCOMPtr 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 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 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 callbackEvent = NS_NewInputStreamReadyEvent( + "ThrottleInputStream::AllowInput", mCallback, mEventTarget); + mCallback = nullptr; + mEventTarget = nullptr; + callbackEvent->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- + +// static +already_AddRefed ThrottleQueue::Create() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr tq; + if (nsIOService::UseSocketProcess()) { + tq = new InputChannelThrottleQueueParent(); + } else { + tq = new ThrottleQueue(); + } + + return tq.forget(); +} + +NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback, + nsINamed) + +ThrottleQueue::ThrottleQueue() + +{ + nsresult rv; + nsCOMPtr sts; + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) { + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + } + if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts); +} + +ThrottleQueue::~ThrottleQueue() { + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; +} + +NS_IMETHODIMP +ThrottleQueue::RecordRead(uint32_t aBytesRead) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + ThrottleEntry entry; + entry.mTime = TimeStamp::Now(); + entry.mBytesRead = aBytesRead; + mReadEvents.AppendElement(entry); + mBytesProcessed += aBytesRead; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + TimeStamp now = TimeStamp::Now(); + TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1); + size_t i; + + // Remove all stale events. + for (i = 0; i < mReadEvents.Length(); ++i) { + if (mReadEvents[i].mTime >= oneSecondAgo) { + break; + } + } + mReadEvents.RemoveElementsAt(0, i); + + uint32_t totalBytes = 0; + for (i = 0; i < mReadEvents.Length(); ++i) { + totalBytes += mReadEvents[i].mBytesRead; + } + + uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond; + double prob = static_cast(rand()) / RAND_MAX; + uint32_t thisSliceBytes = + mMeanBytesPerSecond - spread + static_cast(2 * spread * prob); + + if (totalBytes >= thisSliceBytes) { + *aAvailable = 0; + } else { + *aAvailable = std::min(thisSliceBytes, aRemaining); + } + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) { + // Can be called on any thread. + if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || + aMaxBytesPerSecond < aMeanBytesPerSecond) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mMeanBytesPerSecond = aMeanBytesPerSecond; + mMaxBytesPerSecond = aMaxBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::BytesProcessed(uint64_t* aResult) { + *aResult = mBytesProcessed; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::WrapStream(nsIInputStream* aInputStream, + nsIAsyncInputStream** aResult) { + nsCOMPtr result = + new ThrottleInputStream(aInputStream, this); + result.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Notify(nsITimer* aTimer) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // A notified reader may need to push itself back on the queue. + // Swap out the list of readers so that this works properly. + nsTArray> events = std::move(mAsyncEvents); + + // Optimistically notify all the waiting readers, and then let them + // requeue if there isn't enough bandwidth. + for (size_t i = 0; i < events.Length(); ++i) { + events[i]->AllowInput(); + } + + mTimerArmed = false; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::GetName(nsACString& aName) { + aName.AssignLiteral("net::ThrottleQueue"); + return NS_OK; +} + +void ThrottleQueue::QueueStream(ThrottleInputStream* aStream) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mAsyncEvents.IndexOf(aStream) == + nsTArray>::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((t - now).ToMilliseconds()); + } else { + ms = 1; + } + } + + if (NS_SUCCEEDED( + mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) { + mTimerArmed = true; + } + } + } +} + +void ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mAsyncEvents.RemoveElement(aStream); +} + +NS_IMETHODIMP +ThrottleQueue::GetMeanBytesPerSecond(uint32_t* aMeanBytesPerSecond) { + NS_ENSURE_ARG(aMeanBytesPerSecond); + + *aMeanBytesPerSecond = mMeanBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::GetMaxBytesPerSecond(uint32_t* aMaxBytesPerSecond) { + NS_ENSURE_ARG(aMaxBytesPerSecond); + + *aMaxBytesPerSecond = mMaxBytesPerSecond; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h new file mode 100644 index 0000000000..4d06af8e64 --- /dev/null +++ b/netwerk/base/ThrottleQueue.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ThrottleQueue_h +#define mozilla_net_ThrottleQueue_h + +#include "mozilla/TimeStamp.h" +#include "nsINamed.h" +#include "nsIThrottledInputChannel.h" +#include "nsITimer.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +class ThrottleInputStream; + +/** + * An implementation of nsIInputChannelThrottleQueue that can be used + * to throttle uploads. This class is not thread-safe. + * Initialization and calls to WrapStream may be done on any thread; + * but otherwise, after creation, it can only be used on the socket + * thread. It currently throttles with a one second granularity, so + * may be a bit choppy. + */ + +class ThrottleQueue : public nsIInputChannelThrottleQueue, + public nsITimerCallback, + public nsINamed { + public: + static already_AddRefed Create(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + void QueueStream(ThrottleInputStream* aStream); + void DequeueStream(ThrottleInputStream* aStream); + + protected: + ThrottleQueue(); + virtual ~ThrottleQueue(); + + struct ThrottleEntry { + TimeStamp mTime; + uint32_t mBytesRead = 0; + }; + + nsTArray mReadEvents; + uint32_t mMeanBytesPerSecond{0}; + uint32_t mMaxBytesPerSecond{0}; + uint64_t mBytesProcessed{0}; + + nsTArray> mAsyncEvents; + nsCOMPtr mTimer; + bool mTimerArmed{false}; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ThrottleQueue_h diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp new file mode 100644 index 0000000000..3030b24653 --- /dev/null +++ b/netwerk/base/Tickler.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Tickler.h" + +#ifdef MOZ_USE_WIFI_TICKLER +# include "nsComponentManagerUtils.h" +# include "nsINamed.h" +# include "nsServiceManagerUtils.h" +# include "nsThreadUtils.h" +# include "prnetdb.h" + +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler) + +Tickler::Tickler() + : mLock("Tickler::mLock"), + mActive(false), + mCanceled(false), + mEnabled(false), + mDelay(16), + mDuration(TimeDuration::FromMilliseconds(400)), + mFD(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); +} + +Tickler::~Tickler() { + // non main thread uses of the tickler should hold weak + // references to it if they must hold a reference at all + MOZ_ASSERT(NS_IsMainThread()); + + if (mThread) { + mThread->AsyncShutdown(); + mThread = nullptr; + } + + if (mTimer) mTimer->Cancel(); + if (mFD) PR_Close(mFD); +} + +nsresult Tickler::Init() { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mTimer); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(!mThread); + MOZ_ASSERT(!mFD); + + if (jni::IsAvailable()) { + java::GeckoAppShell::EnableNetworkNotifications(); + } + + mFD = PR_OpenUDPSocket(PR_AF_INET); + if (!mFD) return NS_ERROR_FAILURE; + + // make sure new socket has a ttl of 1 + // failure is not fatal. + PRSocketOptionData opt; + opt.option = PR_SockOpt_IpTimeToLive; + opt.value.ip_ttl = 1; + PR_SetSocketOption(mFD, &opt); + + nsresult rv = NS_NewNamedThread("wifi tickler", getter_AddRefs(mThread)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr tmpTimer = NS_NewTimer(mThread); + if (!tmpTimer) return NS_ERROR_OUT_OF_MEMORY; + + mTimer.swap(tmpTimer); + + mAddr.inet.family = PR_AF_INET; + mAddr.inet.port = PR_htons(4886); + mAddr.inet.ip = 0; + + return NS_OK; +} + +void Tickler::Tickle() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread); + mLastTickle = TimeStamp::Now(); + if (!mActive) MaybeStartTickler(); +} + +void Tickler::PostCheckTickler() { + mLock.AssertCurrentThreadOwns(); + mThread->Dispatch(NewRunnableMethod("net::Tickler::CheckTickler", this, + &Tickler::CheckTickler), + NS_DISPATCH_NORMAL); + return; +} + +void Tickler::MaybeStartTicklerUnlocked() { + MutexAutoLock lock(mLock); + MaybeStartTickler(); +} + +void Tickler::MaybeStartTickler() { + mLock.AssertCurrentThreadOwns(); + if (!NS_IsMainThread()) { + NS_DispatchToMainThread( + NewRunnableMethod("net::Tickler::MaybeStartTicklerUnlocked", this, + &Tickler::MaybeStartTicklerUnlocked)); + return; + } + + if (!mPrefs) mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (mPrefs) { + int32_t val; + bool boolVal; + + if (NS_SUCCEEDED( + mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal))) + mEnabled = boolVal; + + if (NS_SUCCEEDED( + mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) { + if (val < 1) val = 1; + if (val > 100000) val = 100000; + mDuration = TimeDuration::FromMilliseconds(val); + } + + if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) { + if (val < 1) val = 1; + if (val > 1000) val = 1000; + mDelay = static_cast(val); + } + } + + PostCheckTickler(); +} + +void Tickler::CheckTickler() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + + bool shouldRun = + (!mCanceled) && ((TimeStamp::Now() - mLastTickle) <= mDuration); + + if ((shouldRun && mActive) || (!shouldRun && !mActive)) + return; // no change in state + + if (mActive) + StopTickler(); + else + StartTickler(); +} + +void Tickler::Cancel() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(NS_IsMainThread()); + mCanceled = true; + if (mThread) PostCheckTickler(); +} + +void Tickler::StopTickler() { + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(mTimer); + MOZ_ASSERT(mActive); + + mTimer->Cancel(); + mActive = false; +} + +class TicklerTimer final : public nsITimerCallback, public nsINamed { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + explicit TicklerTimer(Tickler* aTickler) { + mTickler = do_GetWeakReference(aTickler); + } + + // nsINamed + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("TicklerTimer"); + return NS_OK; + } + + private: + ~TicklerTimer() {} + + nsWeakPtr mTickler; +}; + +void Tickler::StartTickler() { + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(mTimer); + + if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this), + mEnabled ? mDelay : 1000, + nsITimer::TYPE_REPEATING_SLACK))) + mActive = true; +} + +// argument should be in network byte order +void Tickler::SetIPV4Address(uint32_t address) { mAddr.inet.ip = address; } + +// argument should be in network byte order +void Tickler::SetIPV4Port(uint16_t port) { mAddr.inet.port = port; } + +NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback, nsINamed) + +NS_IMETHODIMP TicklerTimer::Notify(nsITimer* timer) { + RefPtr tickler = do_QueryReferent(mTickler); + if (!tickler) return NS_ERROR_FAILURE; + MutexAutoLock lock(tickler->mLock); + + if (!tickler->mFD) { + tickler->StopTickler(); + return NS_ERROR_FAILURE; + } + + if (tickler->mCanceled || + ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) { + tickler->StopTickler(); + return NS_OK; + } + + if (!tickler->mEnabled) return NS_OK; + + PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0); + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#else // not defined MOZ_USE_WIFI_TICKLER + +namespace mozilla { +namespace net { +NS_IMPL_ISUPPORTS0(Tickler) +} // namespace net +} // namespace mozilla + +#endif // defined MOZ_USE_WIFI_TICKLER diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h new file mode 100644 index 0000000000..264ecefebb --- /dev/null +++ b/netwerk/base/Tickler.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Tickler_h +#define mozilla_net_Tickler_h + +// The tickler sends a regular 0 byte UDP heartbeat out to a +// particular address for a short time after it has been touched. This +// is used on some mobile wifi chipsets to mitigate Power Save Polling +// (PSP) Mode when we are anticipating a response packet +// soon. Typically PSP adds 100ms of latency to a read event because +// the packet delivery is not triggered until the 802.11 beacon is +// delivered to the host (100ms is the standard Access Point +// configuration for the beacon interval.) Requesting a frequent +// transmission and getting a CTS frame from the AP at least that +// frequently allows for low latency receives when we have reason to +// expect them (e.g a SYN-ACK). +// +// The tickler is used to allow RTT based phases of web transport to +// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL +// handshake, HTTP headers, and the TCP slow start phase. The +// transaction is given up to 400 miliseconds by default to get +// through those phases before the tickler is disabled. +// +// The tickler only applies to wifi on mobile right now. Hopefully it +// can also be restricted to particular handset models in the future. + +#if defined(ANDROID) && !defined(MOZ_PROXY_BYPASS_PROTECTION) +# define MOZ_USE_WIFI_TICKLER +#endif + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include + +#ifdef MOZ_USE_WIFI_TICKLER +# include "mozilla/Mutex.h" +# include "mozilla/TimeStamp.h" +# include "nsISupports.h" +# include "nsIThread.h" +# include "nsITimer.h" +# include "nsWeakReference.h" +# include "prio.h" + +class nsIPrefBranch; +#endif + +namespace mozilla { +namespace net { + +#ifdef MOZ_USE_WIFI_TICKLER + +// 8f769ed6-207c-4af9-9f7e-9e832da3754e +# define NS_TICKLER_IID \ + { \ + 0x8f769ed6, 0x207c, 0x4af9, { \ + 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e \ + } \ + } + +class Tickler final : public nsSupportsWeakReference { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID) + + // These methods are main thread only + Tickler(); + void Cancel(); + nsresult Init(); + void SetIPV4Address(uint32_t address); + void SetIPV4Port(uint16_t port); + + // Tickle the tickler to (re-)start the activity. + // May call from any thread + void Tickle(); + + private: + ~Tickler(); + + friend class TicklerTimer; + Mutex mLock MOZ_UNANNOTATED; + nsCOMPtr mThread; + nsCOMPtr mTimer; + nsCOMPtr mPrefs; + + bool mActive; + bool mCanceled; + bool mEnabled; + uint32_t mDelay; + TimeDuration mDuration; + PRFileDesc* mFD; + + TimeStamp mLastTickle; + PRNetAddr mAddr; + + // These functions may be called from any thread + void PostCheckTickler(); + void MaybeStartTickler(); + void MaybeStartTicklerUnlocked(); + + // Tickler thread only + void CheckTickler(); + void StartTickler(); + void StopTickler(); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID) + +#else // not defined MOZ_USE_WIFI_TICKLER + +class Tickler final : public nsISupports { + ~Tickler() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Tickler() = default; + nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; } + void Cancel() {} + void SetIPV4Address(uint32_t){}; + void SetIPV4Port(uint16_t) {} + void Tickle() {} +}; + +#endif // defined MOZ_USE_WIFI_TICKLER + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Tickler_h diff --git a/netwerk/base/ascii_pac_utils.js b/netwerk/base/ascii_pac_utils.js new file mode 100644 index 0000000000..cedb1a0f21 --- /dev/null +++ b/netwerk/base/ascii_pac_utils.js @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global dnsResolve */ + +function dnsDomainIs(host, domain) { + return ( + host.length >= domain.length && + host.substring(host.length - domain.length) == domain + ); +} + +function dnsDomainLevels(host) { + return host.split(".").length - 1; +} + +function isValidIpAddress(ipchars) { + var matches = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipchars); + if (matches == null) { + return false; + } else if ( + matches[1] > 255 || + matches[2] > 255 || + matches[3] > 255 || + matches[4] > 255 + ) { + return false; + } + return true; +} + +function convert_addr(ipchars) { + var bytes = ipchars.split("."); + var result = + ((bytes[0] & 0xff) << 24) | + ((bytes[1] & 0xff) << 16) | + ((bytes[2] & 0xff) << 8) | + (bytes[3] & 0xff); + return result; +} + +function isInNet(ipaddr, pattern, maskstr) { + if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) { + return false; + } + if (!isValidIpAddress(ipaddr)) { + ipaddr = dnsResolve(ipaddr); + if (ipaddr == null) { + return false; + } + } + var host = convert_addr(ipaddr); + var pat = convert_addr(pattern); + var mask = convert_addr(maskstr); + return (host & mask) == (pat & mask); +} + +function isPlainHostName(host) { + return host.search("(\\.)|:") == -1; +} + +function isResolvable(host) { + var ip = dnsResolve(host); + return ip != null; +} + +function localHostOrDomainIs(host, hostdom) { + return host == hostdom || hostdom.lastIndexOf(host + ".", 0) == 0; +} + +function shExpMatch(url, pattern) { + pattern = pattern.replace(/\./g, "\\."); + pattern = pattern.replace(/\*/g, ".*"); + pattern = pattern.replace(/\?/g, "."); + var newRe = new RegExp("^" + pattern + "$"); + return newRe.test(url); +} + +var wdays = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 }; +var months = { + JAN: 0, + FEB: 1, + MAR: 2, + APR: 3, + MAY: 4, + JUN: 5, + JUL: 6, + AUG: 7, + SEP: 8, + OCT: 9, + NOV: 10, + DEC: 11, +}; + +function weekdayRange() { + function getDay(weekday) { + if (weekday in wdays) { + return wdays[weekday]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + var wday; + if (argc < 1) { + return false; + } + if (arguments[argc - 1] == "GMT") { + argc--; + wday = date.getUTCDay(); + } else { + wday = date.getDay(); + } + var wd1 = getDay(arguments[0]); + var wd2 = argc == 2 ? getDay(arguments[1]) : wd1; + if (wd1 == -1 || wd2 == -1) { + return false; + } + + if (wd1 <= wd2) { + return wd1 <= wday && wday <= wd2; + } + + return wd2 >= wday || wday >= wd1; +} + +function dateRange() { + function getMonth(name) { + if (name in months) { + return months[name]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + if (argc < 1) { + return false; + } + var isGMT = arguments[argc - 1] == "GMT"; + + if (isGMT) { + argc--; + } + // function will work even without explict handling of this case + if (argc == 1) { + let tmp = parseInt(arguments[0]); + if (isNaN(tmp)) { + return ( + (isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]) + ); + } else if (tmp < 32) { + return (isGMT ? date.getUTCDate() : date.getDate()) == tmp; + } + return (isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp; + } + var year = date.getFullYear(); + var date1, date2; + date1 = new Date(year, 0, 1, 0, 0, 0); + date2 = new Date(year, 11, 31, 23, 59, 59); + var adjustMonth = false; + for (let i = 0; i < argc >> 1; i++) { + let tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + let mon = getMonth(arguments[i]); + date1.setMonth(mon); + } else if (tmp < 32) { + adjustMonth = argc <= 2; + date1.setDate(tmp); + } else { + date1.setFullYear(tmp); + } + } + for (let i = argc >> 1; i < argc; i++) { + let tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + let mon = getMonth(arguments[i]); + date2.setMonth(mon); + } else if (tmp < 32) { + date2.setDate(tmp); + } else { + date2.setFullYear(tmp); + } + } + if (adjustMonth) { + date1.setMonth(date.getMonth()); + date2.setMonth(date.getMonth()); + } + if (isGMT) { + let tmp = date; + tmp.setFullYear(date.getUTCFullYear()); + tmp.setMonth(date.getUTCMonth()); + tmp.setDate(date.getUTCDate()); + tmp.setHours(date.getUTCHours()); + tmp.setMinutes(date.getUTCMinutes()); + tmp.setSeconds(date.getUTCSeconds()); + date = tmp; + } + return date1 <= date2 + ? date1 <= date && date <= date2 + : date2 >= date || date >= date1; +} + +function timeRange() { + var argc = arguments.length; + var date = new Date(); + var isGMT = false; + if (argc < 1) { + return false; + } + if (arguments[argc - 1] == "GMT") { + isGMT = true; + argc--; + } + + var hour = isGMT ? date.getUTCHours() : date.getHours(); + var date1, date2; + date1 = new Date(); + date2 = new Date(); + + if (argc == 1) { + return hour == arguments[0]; + } else if (argc == 2) { + return arguments[0] <= hour && hour <= arguments[1]; + } + switch (argc) { + case 6: + date1.setSeconds(arguments[2]); + date2.setSeconds(arguments[5]); + // falls through + case 4: + var middle = argc >> 1; + date1.setHours(arguments[0]); + date1.setMinutes(arguments[1]); + date2.setHours(arguments[middle]); + date2.setMinutes(arguments[middle + 1]); + if (middle == 2) { + date2.setSeconds(59); + } + break; + default: + throw new Error("timeRange: bad number of arguments"); + } + + if (isGMT) { + date.setFullYear(date.getUTCFullYear()); + date.setMonth(date.getUTCMonth()); + date.setDate(date.getUTCDate()); + date.setHours(date.getUTCHours()); + date.setMinutes(date.getUTCMinutes()); + date.setSeconds(date.getUTCSeconds()); + } + return date1 <= date2 + ? date1 <= date && date <= date2 + : date2 >= date || date >= date1; +} diff --git a/netwerk/base/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml new file mode 100644 index 0000000000..4625cdf974 --- /dev/null +++ b/netwerk/base/http-sfv/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "http_sfv" +version = "0.1.0" +authors = ["barabass "] +edition = "2018" +license = "MPL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +sfv = "0.9.1" +xpcom = { path = "../../../xpcom/rust/xpcom" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } diff --git a/netwerk/base/http-sfv/SFVService.cpp b/netwerk/base/http-sfv/SFVService.cpp new file mode 100644 index 0000000000..9759993e7f --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "SFVService.h" + +// This anonymous namespace prevents outside C++ code from improperly accessing +// these implementation details. +namespace { +extern "C" { +// Implemented in Rust. +void new_sfv_service(nsISFVService** result); +} + +static mozilla::StaticRefPtr sService; +} // namespace + +namespace mozilla::net { + +already_AddRefed GetSFVService() { + nsCOMPtr service; + + if (sService) { + service = sService; + } else { + new_sfv_service(getter_AddRefs(service)); + sService = service; + mozilla::ClearOnShutdown(&sService); + } + + return service.forget(); +} + +} // namespace mozilla::net diff --git a/netwerk/base/http-sfv/SFVService.h b/netwerk/base/http-sfv/SFVService.h new file mode 100644 index 0000000000..4016951609 --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStructuredFieldValues.h" +namespace mozilla { +namespace net { + +already_AddRefed GetSFVService(); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/http-sfv/moz.build b/netwerk/base/http-sfv/moz.build new file mode 100644 index 0000000000..ed857c91a8 --- /dev/null +++ b/netwerk/base/http-sfv/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "nsIStructuredFieldValues.idl", +] + +XPIDL_MODULE = "http-sfv" + +EXPORTS.mozilla.net += ["SFVService.h"] + +SOURCES += ["SFVService.cpp"] + +FINAL_LIBRARY = "xul" diff --git a/netwerk/base/http-sfv/nsIStructuredFieldValues.idl b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl new file mode 100644 index 0000000000..3f02b33953 --- /dev/null +++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl @@ -0,0 +1,290 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Conceptually, there are three types of structured field (header) values: + * + * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean. + * It can have associated Parameters. + * List - array of zero or more members, each of which can be an Item or an InnerList, + * both of which can be Parameterized. + * Dictionary - ordered map of name-value pairs, where the names are short textual strings + * and the values are Items or arrays of Items (represented with InnerList), + * both of which can be Parameterized. There can be zero or more members, + * and their names are unique in the scope of the Dictionary they occur within. + * + * + * There's also a few primitive types used to construct structured field values: + * - BareItem used as Item's value or as a parameter value in Parameters. + * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList. + * The keys are unique within the scope the Parameters they occur within, and the values are BareItem. + * - InnerList is an array of zero or more Items. Can have Parameters. + * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary. + */ + + + +/** + * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams). + * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE. + * Each type is represented by its own interface which is used to create + * a bare item of that type. + */ +[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)] +interface nsISFVBareItem : nsISupports { + const long BOOL = 1; + const long STRING = 2; + const long DECIMAL = 3; + const long INTEGER = 4; + const long TOKEN = 5; + const long BYTE_SEQUENCE = 6; + + /** + * Returns value associated with type of bare item. + * Used to identify type of bare item without querying for interface + * (like nsISFVString, etc). + */ + readonly attribute long type; +}; + +[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)] +interface nsISFVInteger : nsISFVBareItem { + attribute long long value; +}; + +[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)] +interface nsISFVString : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)] +interface nsISFVBool : nsISFVBareItem { + attribute boolean value; +}; + +[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)] +interface nsISFVDecimal : nsISFVBareItem { + attribute double value; +}; + +[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)] +interface nsISFVToken : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)] +interface nsISFVByteSeq : nsISFVBareItem { + attribute ACString value; +}; + + +/** + * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem, + * which parametrize Item type header or InnerList type withing List type header. + */ +[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)] +interface nsISFVParams : nsISupports { + /** + * Return value (nsISFVBareItem) stored for key, if it is present + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVBareItem get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface. + */ + void set(in ACString key, in nsISFVBareItem item); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array keys(); +}; + +/** + * nsISFVParametrizable is implemented for types that + * can be parametrized with nsISFVParams + */ +[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)] +interface nsISFVParametrizable : nsISupports { + readonly attribute nsISFVParams params; +}; + +/** + * nsISFVItemOrInnerList represents member in nsISFVList + * or member-value in nsISFVDictionary. + * nsISFVItemOrInnerList is implemented for + * nsISFVItem or nsISFVInnerList, both of which are used + * to create nsISFVList and nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)] +interface nsISFVItemOrInnerList : nsISFVParametrizable { +}; + +/** + * nsISFVSerialize indicates that object can be serialized into ACString. + */ +[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)] +interface nsISFVSerialize : nsISupports { + ACString serialize(); +}; + +/** + * nsISFVItem represents Item structured header value. + */ +[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)] +interface nsISFVItem : nsISFVItemOrInnerList { + readonly attribute nsISFVBareItem value; + ACString serialize(); +}; + +/** + * nsISFVInnerList can be used as a member of nsISFVList + * or a member-value of nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)] +interface nsISFVInnerList : nsISFVItemOrInnerList { + attribute Array items; +}; + +/** + * nsISFVList represents List structured header value. + */ +[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)] +interface nsISFVList : nsISFVSerialize { + /** + * Returns array of members. + * QueryInterface can be used on a member to get more specific type. + */ + attribute Array members; + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + +/** + * nsISFVDictionary represents nsISFVDictionary structured header value. + */ +[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)] +interface nsISFVDictionary : nsISFVSerialize { + + /** + * Return value (nsISFVItemOrInnerList) stored for key, if it is present. + * QueryInterface can be used on a value to get more specific type. + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVItemOrInnerList get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface. + */ + void set(in ACString key, in nsISFVItemOrInnerList member_value); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array keys(); + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + + +/** + * nsISFVService provides a set of functions for working with HTTP header value as an object. + * It exposes functions for creating object from string containing header value, + * as well as individual components for manual structured header object creation. + */ +[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)] +interface nsISFVService: nsISupports +{ + /** + * Parses provided string into Dictionary header value (nsISFVDictionary). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVDictionary parseDictionary(in ACString header); + + /** + * Parses provided string into List header value (nsISFVList). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVList parseList(in ACString header); + + /** + * Parses provided string into Item header value (nsISFVItem). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVItem parseItem(in ACString header); + + /** + * The following functions create bare item of specific type. + */ + nsISFVInteger newInteger(in long long value); + nsISFVBool newBool(in bool value); + nsISFVDecimal newDecimal(in double value); + nsISFVString newString(in ACString value); + nsISFVByteSeq newByteSequence(in ACString value); + nsISFVToken newToken(in ACString value); + + /** + * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default. + */ + nsISFVParams newParameters(); + + /** + * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams. + */ + nsISFVInnerList newInnerList(in Array items, in nsISFVParams params); + + /** + * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams. + */ + nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params); + + /** + * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList. + * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList). + */ + nsISFVList newList(in Array members); + + /** + * Creates nsISFVDictionary representing Dictionary header value. It is empty by default. + */ + nsISFVDictionary newDictionary(); +}; diff --git a/netwerk/base/http-sfv/src/lib.rs b/netwerk/base/http-sfv/src/lib.rs new file mode 100644 index 0000000000..fe669f5f3f --- /dev/null +++ b/netwerk/base/http-sfv/src/lib.rs @@ -0,0 +1,873 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK}; +use nsstring::{nsACString, nsCString}; +use sfv::Parser; +use sfv::SerializeValue; +use sfv::{ + BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters, + ParseMore, +}; +use std::cell::RefCell; +use std::ops::Deref; +use thin_vec::ThinVec; +use xpcom::interfaces::{ + nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList, + nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService, + nsISFVString, nsISFVToken, +}; +use xpcom::{xpcom, xpcom_method, RefPtr, XpCom}; + +#[no_mangle] +pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) { + let service: RefPtr = SFVService::new(); + RefPtr::new(service.coerce::()).forget(&mut *result); +} + +#[xpcom(implement(nsISFVService), atomic)] +struct SFVService {} + +impl SFVService { + fn new() -> RefPtr { + SFVService::allocate(InitSFVService {}) + } + + xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary); + fn parse_dictionary(&self, header: &nsACString) -> Result, nsresult> { + let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?; + let sfv_dict = SFVDictionary::new(); + sfv_dict.value.replace(parsed_dict); + Ok(RefPtr::new(sfv_dict.coerce::())) + } + + xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList); + fn parse_list(&self, header: &nsACString) -> Result, nsresult> { + let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?; + + let mut nsi_members = Vec::new(); + for item_or_inner_list in parsed_list.iter() { + nsi_members.push(interface_from_list_entry(item_or_inner_list)?) + } + let sfv_list = SFVList::allocate(InitSFVList { + members: RefCell::new(nsi_members), + }); + Ok(RefPtr::new(sfv_list.coerce::())) + } + + xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem); + fn parse_item(&self, header: &nsACString) -> Result, nsresult> { + let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?; + interface_from_item(&parsed_item) + } + + xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger); + fn new_integer(&self, value: i64) -> Result, nsresult> { + Ok(RefPtr::new( + SFVInteger::new(value).coerce::(), + )) + } + + xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal); + fn new_decimal(&self, value: f64) -> Result, nsresult> { + Ok(RefPtr::new( + SFVDecimal::new(value).coerce::(), + )) + } + + xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool); + fn new_bool(&self, value: bool) -> Result, nsresult> { + Ok(RefPtr::new(SFVBool::new(value).coerce::())) + } + + xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString); + fn new_string(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new(SFVString::new(value).coerce::())) + } + + xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken); + fn new_token(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new(SFVToken::new(value).coerce::())) + } + + xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq); + fn new_byte_sequence(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new( + SFVByteSeq::new(value).coerce::(), + )) + } + + xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams); + fn new_parameters(&self) -> Result, nsresult> { + Ok(RefPtr::new(SFVParams::new().coerce::())) + } + + xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params: *const nsISFVParams) -> *const nsISFVItem); + fn new_item( + &self, + value: &nsISFVBareItem, + params: &nsISFVParams, + ) -> Result, nsresult> { + Ok(RefPtr::new( + SFVItem::new(value, params).coerce::(), + )) + } + + xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec>>, params: *const nsISFVParams) -> *const nsISFVInnerList); + fn new_inner_list( + &self, + items: &thin_vec::ThinVec>>, + params: &nsISFVParams, + ) -> Result, nsresult> { + let items = items + .iter() + .cloned() + .map(|item| item.ok_or(NS_ERROR_NULL_POINTER)) + .collect::, nsresult>>()?; + Ok(RefPtr::new( + SFVInnerList::new(items, params).coerce::(), + )) + } + + xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec>>) -> *const nsISFVList); + fn new_list( + &self, + members: &thin_vec::ThinVec>>, + ) -> Result, nsresult> { + let members = members + .iter() + .cloned() + .map(|item| item.ok_or(NS_ERROR_NULL_POINTER)) + .collect::, nsresult>>()?; + Ok(RefPtr::new(SFVList::new(members).coerce::())) + } + + xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary); + fn new_dictionary(&self) -> Result, nsresult> { + Ok(RefPtr::new( + SFVDictionary::new().coerce::(), + )) + } +} + +#[xpcom(implement(nsISFVInteger, nsISFVBareItem), nonatomic)] +struct SFVInteger { + value: RefCell, +} + +impl SFVInteger { + fn new(value: i64) -> RefPtr { + SFVInteger::allocate(InitSFVInteger { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> i64); + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: i64)); + fn set_value(&self, value: i64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::INTEGER) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVBool, nsISFVBareItem), nonatomic)] +struct SFVBool { + value: RefCell, +} + +impl SFVBool { + fn new(value: bool) -> RefPtr { + SFVBool::allocate(InitSFVBool { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> bool); + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: bool)); + fn set_value(&self, value: bool) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::BOOL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVString, nsISFVBareItem), nonatomic)] +struct SFVString { + value: RefCell, +} + +impl SFVString { + fn new(value: &nsACString) -> RefPtr { + SFVString::allocate(InitSFVString { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::STRING) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVToken, nsISFVBareItem), nonatomic)] +struct SFVToken { + value: RefCell, +} + +impl SFVToken { + fn new(value: &nsACString) -> RefPtr { + SFVToken::allocate(InitSFVToken { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::TOKEN) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVByteSeq, nsISFVBareItem), nonatomic)] +struct SFVByteSeq { + value: RefCell, +} + +impl SFVByteSeq { + fn new(value: &nsACString) -> RefPtr { + SFVByteSeq::allocate(InitSFVByteSeq { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::BYTE_SEQUENCE) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVDecimal, nsISFVBareItem), nonatomic)] +struct SFVDecimal { + value: RefCell, +} + +impl SFVDecimal { + fn new(value: f64) -> RefPtr { + SFVDecimal::allocate(InitSFVDecimal { + value: RefCell::new(value), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> f64 + ); + + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!( + set_value => SetValue(value: f64) + ); + + fn set_value(&self, value: f64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::DECIMAL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVParams), nonatomic)] +struct SFVParams { + params: RefCell, +} + +impl SFVParams { + fn new() -> RefPtr { + SFVParams::allocate(InitSFVParams { + params: RefCell::new(Parameters::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVBareItem + ); + + fn get(&self, key: &nsACString) -> Result, nsresult> { + let key = key.to_utf8(); + let params = self.params.borrow(); + let param_val = params.get(key.as_ref()); + + match param_val { + Some(val) => interface_from_bare_item(val), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVBareItem) + ); + + fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let bare_item = bare_item_from_interface(item)?; + self.params.borrow_mut().insert(key, bare_item); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + let mut params = self.params.borrow_mut(); + + if !params.contains_key(key.as_ref()) { + return Err(NS_ERROR_UNEXPECTED); + } + + // Keeps only entries that don't match key + params.retain(|k, _| k != key.as_ref()); + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec + ); + fn keys(&self) -> Result, nsresult> { + let keys = self.params.borrow(); + let keys = keys + .keys() + .map(nsCString::from) + .collect::>(); + Ok(keys) + } + + fn from_interface(obj: &nsISFVParams) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVItem, nsISFVItemOrInnerList), nonatomic)] +struct SFVItem { + value: RefPtr, + params: RefPtr, +} + +impl SFVItem { + fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr { + SFVItem::allocate(InitSFVItem { + value: RefPtr::new(value), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> *const nsISFVBareItem + ); + + fn get_value(&self) -> Result, nsresult> { + Ok(self.value.clone()) + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result, nsresult> { + Ok(self.params.clone()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let bare_item = bare_item_from_interface(self.value.deref())?; + let params = params_from_interface(self.params.deref())?; + let serialized = Item::with_params(bare_item, params) + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } + + fn from_interface(obj: &nsISFVItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVInnerList, nsISFVItemOrInnerList), nonatomic)] +struct SFVInnerList { + items: RefCell>>, + params: RefPtr, +} + +impl SFVInnerList { + fn new(items: Vec>, params: &nsISFVParams) -> RefPtr { + SFVInnerList::allocate(InitSFVInnerList { + items: RefCell::new(items), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_items => GetItems() -> thin_vec::ThinVec>> + ); + + fn get_items(&self) -> Result>>, nsresult> { + let items = self.items.borrow().iter().cloned().map(Some).collect(); + Ok(items) + } + + #[allow(non_snake_case)] + unsafe fn SetItems( + &self, + value: *const thin_vec::ThinVec>>, + ) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + match (*value) + .iter() + .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER)) + .collect::, nsresult>>() + { + Ok(value) => *self.items.borrow_mut() = value, + Err(rv) => return rv, + } + NS_OK + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result, nsresult> { + Ok(self.params.clone()) + } + + fn from_interface(obj: &nsISFVInnerList) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVList, nsISFVSerialize), nonatomic)] +struct SFVList { + members: RefCell>>, +} + +impl SFVList { + fn new(members: Vec>) -> RefPtr { + SFVList::allocate(InitSFVList { + members: RefCell::new(members), + }) + } + + xpcom_method!( + get_members => GetMembers() -> thin_vec::ThinVec>> + ); + + fn get_members( + &self, + ) -> Result>>, nsresult> { + Ok(self.members.borrow().iter().cloned().map(Some).collect()) + } + + #[allow(non_snake_case)] + unsafe fn SetMembers( + &self, + value: *const thin_vec::ThinVec>>, + ) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + match (*value) + .iter() + .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER)) + .collect::, nsresult>>() + { + Ok(value) => *self.members.borrow_mut() = value, + Err(rv) => return rv, + } + NS_OK + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + // create List from SFVList to call parse_more on it + let mut list = List::new(); + let members = self.members.borrow().clone(); + for interface_entry in members.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?; + + // replace SFVList's members with new_members + let mut new_members = Vec::new(); + for item_or_inner_list in list.iter() { + new_members.push(interface_from_list_entry(item_or_inner_list)?) + } + self.members.replace(new_members); + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let mut list = List::new(); + let members = self.members.borrow().clone(); + for interface_entry in members.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +#[xpcom(implement(nsISFVDictionary, nsISFVSerialize), nonatomic)] +struct SFVDictionary { + value: RefCell, +} + +impl SFVDictionary { + fn new() -> RefPtr { + SFVDictionary::allocate(InitSFVDictionary { + value: RefCell::new(Dictionary::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList + ); + + fn get(&self, key: &nsACString) -> Result, nsresult> { + let key = key.to_utf8(); + let value = self.value.borrow(); + let member_value = value.get(key.as_ref()); + + match member_value { + Some(member) => interface_from_list_entry(member), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList) + ); + + fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let value = list_entry_from_interface(member_value)?; + self.value.borrow_mut().insert(key, value); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + let mut params = self.value.borrow_mut(); + + if !params.contains_key(key.as_ref()) { + return Err(NS_ERROR_UNEXPECTED); + } + + // Keeps only entries that don't match key + params.retain(|k, _| k != key.as_ref()); + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec + ); + fn keys(&self) -> Result, nsresult> { + let members = self.value.borrow(); + let keys = members + .keys() + .map(nsCString::from) + .collect::>(); + Ok(keys) + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + let _ = self + .value + .borrow_mut() + .parse_more(&header) + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let serialized = self + .value + .borrow() + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result { + let obj = obj + .query_interface::() + .ok_or(NS_ERROR_UNEXPECTED)?; + let mut obj_type: i32 = -1; + unsafe { + obj.deref().GetType(&mut obj_type); + } + + match obj_type { + nsISFVBareItem::BOOL => { + let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Boolean(item_value)) + } + nsISFVBareItem::STRING => { + let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*string_itm.to_utf8()).to_string(); + Ok(BareItem::String(item_value)) + } + nsISFVBareItem::TOKEN => { + let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::Token(item_value)) + } + nsISFVBareItem::INTEGER => { + let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Integer(item_value)) + } + nsISFVBareItem::DECIMAL => { + let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?; + let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?; + Ok(BareItem::Decimal(decimal)) + } + nsISFVBareItem::BYTE_SEQUENCE => { + let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?; + let item_value: String = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::ByteSeq(item_value.into_bytes())) + } + _ => return Err(NS_ERROR_UNEXPECTED), + } +} + +fn params_from_interface(obj: &nsISFVParams) -> Result { + let params = SFVParams::from_interface(obj).params.borrow(); + Ok(params.clone()) +} + +fn item_from_interface(obj: &nsISFVItem) -> Result { + let sfv_item = SFVItem::from_interface(obj); + let bare_item = bare_item_from_interface(sfv_item.value.deref())?; + let parameters = params_from_interface(sfv_item.params.deref())?; + Ok(Item::with_params(bare_item, parameters)) +} + +fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result { + let sfv_inner_list = SFVInnerList::from_interface(obj); + + let mut inner_list_items: Vec = vec![]; + for item in sfv_inner_list.items.borrow().iter() { + let item = item_from_interface(item)?; + inner_list_items.push(item); + } + let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?; + Ok(InnerList::with_params(inner_list_items, inner_list_params)) +} + +fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result { + if let Some(nsi_item) = obj.query_interface::() { + let item = item_from_interface(nsi_item.deref())?; + Ok(ListEntry::Item(item)) + } else if let Some(nsi_inner_list) = obj.query_interface::() { + let inner_list = inner_list_from_interface(nsi_inner_list.deref())?; + Ok(ListEntry::InnerList(inner_list)) + } else { + return Err(NS_ERROR_UNEXPECTED); + } +} + +fn interface_from_bare_item(bare_item: &BareItem) -> Result, nsresult> { + let bare_item = match bare_item { + BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::()), + BareItem::String(val) => { + RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::()) + } + BareItem::Token(val) => { + RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::()) + } + BareItem::ByteSeq(val) => RefPtr::new( + SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap())) + .coerce::(), + ), + BareItem::Decimal(val) => { + let val = val + .to_string() + .parse::() + .map_err(|_| NS_ERROR_UNEXPECTED)?; + RefPtr::new(SFVDecimal::new(val).coerce::()) + } + BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::()), + }; + + Ok(bare_item) +} + +fn interface_from_item(item: &Item) -> Result, nsresult> { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::(), + )) +} + +fn interface_from_params(params: &Parameters) -> Result, nsresult> { + let sfv_params = SFVParams::new(); + for (key, value) in params.iter() { + sfv_params + .params + .borrow_mut() + .insert(key.clone(), value.clone()); + } + Ok(RefPtr::new(sfv_params.coerce::())) +} + +fn interface_from_list_entry( + member: &ListEntry, +) -> Result, nsresult> { + match member { + ListEntry::Item(item) => { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::(), + )) + } + ListEntry::InnerList(inner_list) => { + let mut nsi_inner_list = Vec::new(); + for item in inner_list.items.iter() { + let nsi_item = interface_from_item(item)?; + nsi_inner_list.push(nsi_item); + } + + let nsi_params = interface_from_params(&inner_list.params)?; + Ok(RefPtr::new( + SFVInnerList::new(nsi_inner_list, &nsi_params).coerce::(), + )) + } + } +} diff --git a/netwerk/base/makecppstring.py b/netwerk/base/makecppstring.py new file mode 100644 index 0000000000..330fcfb04c --- /dev/null +++ b/netwerk/base/makecppstring.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + + +def main(output, filename): + file = open(filename, "r") + output.write('R"(') # insert literal start + for line in file: + output.write(line) + output.write(')"') # insert literal end + + +if __name__ == "__main__": + main(sys.stdout, sys.argv[1]) diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build new file mode 100644 index 0000000000..d35714df21 --- /dev/null +++ b/netwerk/base/moz.build @@ -0,0 +1,336 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "mozIThirdPartyUtil.idl", + "nsIArrayBufferInputStream.idl", + "nsIAsyncStreamCopier.idl", + "nsIAsyncStreamCopier2.idl", + "nsIAsyncVerifyRedirectCallback.idl", + "nsIAuthInformation.idl", + "nsIAuthModule.idl", + "nsIAuthPrompt.idl", + "nsIAuthPrompt2.idl", + "nsIAuthPromptAdapterFactory.idl", + "nsIAuthPromptCallback.idl", + "nsIAuthPromptProvider.idl", + "nsIBackgroundFileSaver.idl", + "nsIBufferedStreams.idl", + "nsIByteRangeRequest.idl", + "nsICacheInfoChannel.idl", + "nsICachingChannel.idl", + "nsICancelable.idl", + "nsICaptivePortalService.idl", + "nsIChannel.idl", + "nsIChannelEventSink.idl", + "nsIChildChannel.idl", + "nsIClassifiedChannel.idl", + "nsIClassOfService.idl", + "nsIContentSniffer.idl", + "nsIDashboard.idl", + "nsIDashboardEventNotifier.idl", + "nsIDHCPClient.idl", + "nsIDownloader.idl", + "nsIEncodedChannel.idl", + "nsIExternalProtocolHandler.idl", + "nsIFileStreams.idl", + "nsIFileURL.idl", + "nsIForcePendingChannel.idl", + "nsIFormPOSTActionChannel.idl", + "nsIHttpAuthenticatorCallback.idl", + "nsIHttpPushListener.idl", + "nsIIncrementalDownload.idl", + "nsIIncrementalStreamLoader.idl", + "nsIInputStreamChannel.idl", + "nsIInputStreamPump.idl", + "nsIInterceptionInfo.idl", + "nsIIOService.idl", + "nsILoadContextInfo.idl", + "nsILoadGroup.idl", + "nsILoadGroupChild.idl", + "nsILoadInfo.idl", + "nsIMIMEInputStream.idl", + "nsIMultiPartChannel.idl", + "nsINestedURI.idl", + "nsINetAddr.idl", + "nsINetUtil.idl", + "nsINetworkConnectivityService.idl", + "nsINetworkInfoService.idl", + "nsINetworkInterceptController.idl", + "nsINetworkLinkService.idl", + "nsINetworkPredictor.idl", + "nsINetworkPredictorVerifier.idl", + "nsINullChannel.idl", + "nsIParentChannel.idl", + "nsIParentRedirectingChannel.idl", + "nsIPermission.idl", + "nsIPermissionManager.idl", + "nsIPrivateBrowsingChannel.idl", + "nsIProgressEventSink.idl", + "nsIPrompt.idl", + "nsIProtocolHandler.idl", + "nsIProtocolProxyCallback.idl", + "nsIProtocolProxyFilter.idl", + "nsIProtocolProxyService.idl", + "nsIProtocolProxyService2.idl", + "nsIProxiedChannel.idl", + "nsIProxiedProtocolHandler.idl", + "nsIProxyInfo.idl", + "nsIRandomGenerator.idl", + "nsIRedirectChannelRegistrar.idl", + "nsIRedirectHistoryEntry.idl", + "nsIRedirectResultListener.idl", + "nsIRequest.idl", + "nsIRequestContext.idl", + "nsIRequestObserver.idl", + "nsIRequestObserverProxy.idl", + "nsIResumableChannel.idl", + "nsISecCheckWrapChannel.idl", + "nsISecureBrowserUI.idl", + "nsISensitiveInfoHiddenURI.idl", + "nsISerializationHelper.idl", + "nsIServerSocket.idl", + "nsISimpleStreamListener.idl", + "nsISimpleURIMutator.idl", + "nsISocketFilter.idl", + "nsISocketTransport.idl", + "nsISocketTransportService.idl", + "nsISpeculativeConnect.idl", + "nsIStandardURL.idl", + "nsIStreamListener.idl", + "nsIStreamListenerTee.idl", + "nsIStreamLoader.idl", + "nsIStreamTransportService.idl", + "nsISyncStreamListener.idl", + "nsISystemProxySettings.idl", + "nsIThreadRetargetableRequest.idl", + "nsIThreadRetargetableStreamListener.idl", + "nsIThrottledInputChannel.idl", + "nsITimedChannel.idl", + "nsITLSServerSocket.idl", + "nsITraceableChannel.idl", + "nsITransport.idl", + "nsIUDPSocket.idl", + "nsIUploadChannel.idl", + "nsIUploadChannel2.idl", + "nsIURI.idl", + "nsIURIMutator.idl", + "nsIURIWithSpecialOrigin.idl", + "nsIURL.idl", + "nsIURLParser.idl", + "nsPISocketTransportService.idl", +] + +XPIDL_MODULE = "necko" + +EXPORTS += [ + "netCore.h", + "nsASocketHandler.h", + "nsAsyncRedirectVerifyHelper.h", + "nsBaseChannel.h", + "nsFileStreams.h", + "nsInputStreamPump.h", + "nsMIMEInputStream.h", + "nsNetUtil.h", + "nsReadLine.h", + "nsSerializationHelper.h", + "nsSimpleNestedURI.h", + "nsSimpleURI.h", + "nsStandardURL.h", + "nsStreamListenerWrapper.h", + "nsURIHashKey.h", + "nsURLHelper.h", + "nsURLParsers.h", + "SimpleChannel.h", +] + +EXPORTS.mozilla += [ + "LoadContextInfo.h", + "LoadInfo.h", + "LoadTainting.h", + "nsRedirectHistoryEntry.h", +] + +EXPORTS.mozilla.net += [ + "CacheInfoIPCTypes.h", + "CaptivePortalService.h", + "Dashboard.h", + "DashboardTypes.h", + "DefaultURI.h", + "InterceptionInfo.h", + "IOActivityMonitor.h", + "MemoryDownloader.h", + "NetworkConnectivityService.h", + "Predictor.h", + "PrivateBrowsingChannel.h", + "ProtocolHandlerInfo.h", + "RedirectChannelRegistrar.h", + "RequestContextService.h", + "SimpleChannelParent.h", + "SSLTokensCache.h", + "ThrottleQueue.h", +] + +UNIFIED_SOURCES += [ + "ArrayBufferInputStream.cpp", + "BackgroundFileSaver.cpp", + "CaptivePortalService.cpp", + "Dashboard.cpp", + "DefaultURI.cpp", + "EventTokenBucket.cpp", + "InterceptionInfo.cpp", + "IOActivityMonitor.cpp", + "LoadContextInfo.cpp", + "LoadInfo.cpp", + "MemoryDownloader.cpp", + "NetworkConnectivityService.cpp", + "NetworkDataCountLayer.cpp", + "nsAsyncRedirectVerifyHelper.cpp", + "nsAsyncStreamCopier.cpp", + "nsAuthInformationHolder.cpp", + "nsBase64Encoder.cpp", + "nsBaseChannel.cpp", + "nsBaseContentStream.cpp", + "nsBufferedStreams.cpp", + "nsDirectoryIndexStream.cpp", + "nsDNSPrefetch.cpp", + "nsDownloader.cpp", + "nsFileStreams.cpp", + "nsIncrementalDownload.cpp", + "nsIncrementalStreamLoader.cpp", + "nsInputStreamChannel.cpp", + "nsInputStreamPump.cpp", + "nsIOService.cpp", + "nsIURIMutatorUtils.cpp", + "nsLoadGroup.cpp", + "nsMIMEInputStream.cpp", + "nsNetAddr.cpp", + "nsNetUtil.cpp", + "nsPACMan.cpp", + "nsPreloadedStream.cpp", + "nsProtocolProxyService.cpp", + "nsProxyInfo.cpp", + "nsRedirectHistoryEntry.cpp", + "nsRequestObserverProxy.cpp", + "nsSerializationHelper.cpp", + "nsServerSocket.cpp", + "nsSimpleNestedURI.cpp", + "nsSimpleStreamListener.cpp", + "nsSimpleURI.cpp", + "nsSocketTransport2.cpp", + "nsSocketTransportService2.cpp", + "nsStandardURL.cpp", + "nsStreamListenerTee.cpp", + "nsStreamListenerWrapper.cpp", + "nsStreamLoader.cpp", + "nsStreamTransportService.cpp", + "nsSyncStreamListener.cpp", + "nsTransportUtils.cpp", + "nsUDPSocket.cpp", + "PollableEvent.cpp", + "Predictor.cpp", + "ProtocolHandlerInfo.cpp", + "ProxyAutoConfig.cpp", + "RedirectChannelRegistrar.cpp", + "RequestContextService.cpp", + "SimpleBuffer.cpp", + "SimpleChannel.cpp", + "SimpleChannelParent.cpp", + "SSLTokensCache.cpp", + "ThrottleQueue.cpp", + "Tickler.cpp", + "TLSServerSocket.cpp", + "TRRLoadInfo.cpp", +] + +if CONFIG["FUZZING"]: + SOURCES += [ + "FuzzyLayer.cpp", + "FuzzySecurityInfo.cpp", + "FuzzySocketControl.cpp", + ] + +if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]: + include("/tools/fuzzing/libfuzzer-flags.mozbuild") + SOURCES += [ + "nsMediaFragmentURIParser.cpp", + "nsURLHelper.cpp", + "nsURLParsers.cpp", + ] + SOURCES["nsMediaFragmentURIParser.cpp"].flags += libfuzzer_flags + SOURCES["nsURLHelper.cpp"].flags += libfuzzer_flags + SOURCES["nsURLParsers.cpp"].flags += libfuzzer_flags +else: + UNIFIED_SOURCES += [ + "nsMediaFragmentURIParser.cpp", + "nsURLHelper.cpp", + "nsURLParsers.cpp", + ] + + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "nsURLHelperWin.cpp", + "ShutdownLayer.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsURLHelperOSX.cpp", + ] +else: + SOURCES += [ + "nsURLHelperUnix.cpp", + ] + +# 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.sys.mjs", +] + +DIRS += ["mozurl", "rust-helper", "http-sfv"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!/xpcom/components", + "/docshell/base", + "/dom/base", + "/js/xpconnect/src", + "/netwerk/dns", + "/netwerk/protocol/http", + "/netwerk/protocol/webtransport", + "/netwerk/socket", + "/netwerk/url-classifier", + "/security/manager/ssl", + "/xpcom/components", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/xpcom/base", + ] + +GeneratedFile( + "ascii_pac_utils.inc", script="makecppstring.py", inputs=["ascii_pac_utils.js"] +) diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl new file mode 100644 index 0000000000..0c029942d8 --- /dev/null +++ b/netwerk/base/mozIThirdPartyUtil.idl @@ -0,0 +1,231 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface mozIDOMWindowProxy; +interface nsIChannel; +interface nsIPrincipal; +interface nsILoadInfo; + +%{C++ + +#include "mozilla/EnumSet.h" + +enum class ThirdPartyAnalysis { + IsForeign, + IsThirdPartyTrackingResource, + IsThirdPartySocialTrackingResource, + IsStorageAccessPermissionGranted, +}; + +using ThirdPartyAnalysisResult = mozilla::EnumSet; + +typedef bool (*RequireThirdPartyCheck)(nsILoadInfo*); + +%} + +native ThirdPartyAnalysisResult(ThirdPartyAnalysisResult); +native RequireThirdPartyCheck(RequireThirdPartyCheck); + +/** + * Utility functions for determining whether a given URI, channel, or window + * hierarchy is third party with respect to a known URI. + */ +[scriptable, builtinclass, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)] +interface mozIThirdPartyUtil : nsISupports +{ + /** + * isThirdPartyURI + * + * Determine whether two URIs are third party with respect to each other. + * This is determined by computing the base domain for both URIs. If they can + * be determined, and the base domains match, the request is defined as first + * party. If it cannot be determined because one or both URIs do not have a + * base domain (for instance, in the case of IP addresses, host aliases such + * as 'localhost', or a file:// URI), an exact string comparison on host is + * performed. + * + * For example, the URI "http://mail.google.com/" is not third party with + * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and + * "http://192.168.1.1/" are. + * + * @return true if aFirstURI is third party with respect to aSecondURI. + * + * @throws if either URI is null, has a malformed host, or has an empty host + * and is not a file:// URI. + */ + boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI); + + /** + * isThirdPartyWindow + * + * Determine whether the given window hierarchy is third party. This is done + * as follows: + * + * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the + * 'bottom URI'. + * 2) If 'aURI' is provided, determine if it is third party with respect to + * the bottom URI. If so, return. + * 3) Find the same-type parent window, if there is one, and its URI. + * Determine whether it is third party with respect to the bottom URI. If + * so, return. + * + * Therefore, each level in the window hierarchy is tested. (This means that + * nested iframes with different base domains, even though the bottommost and + * topmost URIs might be equal, will be considered third party.) + * + * @param aWindow + * The bottommost window in the hierarchy. + * @param aURI + * A URI to test against. If null, the URI of the principal + * associated with 'aWindow' will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if 'aURI' is third party with respect to any of the URIs + * associated with aWindow and its same-type parents. + * + * @throws if aWindow is null; the same-type parent of any window in the + * hierarchy cannot be determined; or the URI associated with any + * window in the hierarchy is null, has a malformed host, or has an + * empty host and is not a file:// URI. + * + * @see isThirdPartyURI + */ + boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI); + + /** + * isThirdPartyChannel + * + * Determine whether the given channel and its content window hierarchy is + * third party. This is done as follows: + * + * 1) If 'aChannel' is an nsIHttpChannel and has the + * 'forceAllowThirdPartyCookie' property set, then: + * a) If 'aURI' is null, return false. + * b) Otherwise, find the URI of the channel, determine whether it is + * foreign with respect to 'aURI', and return. + * 2) Find the URI of the channel and determine whether it is third party with + * respect to the URI of the channel. If so, return. + * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it + * exists, from the channel's notification callbacks. Then: + * a) If the parent is the same as the bottommost window, and the channel + * has the LOAD_DOCUMENT_URI flag set, return false. This represents the + * case where a toplevel load is occurring and the window's URI has not + * yet been updated. (We have already checked that 'aURI' is not foreign + * with respect to the channel URI.) + * b) Otherwise, return the result of isThirdPartyWindow with arguments + * of the channel's bottommost window and the channel URI, respectively. + * + * Therefore, both the channel's URI and each level in the window hierarchy + * associated with the channel is tested. + * + * @param aChannel + * The channel associated with the load. + * @param aURI + * A URI to test against. If null, the URI of the channel will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if aURI is third party with respect to the channel URI or any + * of the URIs associated with the same-type window hierarchy of the + * channel. + * + * @throws if 'aChannel' is null; the channel has no notification callbacks or + * an associated window; or isThirdPartyWindow throws. + * + * @see isThirdPartyWindow + */ + boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI); + + /** + * getBaseDomain + * + * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be + * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing + * dot may be present. If aHostURI is an IP address, an alias such as + * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will + * be the exact host. The result of this function should only be used in exact + * string comparisons, since substring comparisons will not be valid for the + * special cases elided above. + * + * @param aHostURI + * The URI to analyze. + * + * @return the base domain. + */ + AUTF8String getBaseDomain(in nsIURI aHostURI); + + /** + * NOTE: Long term, this method won't be needed once bug 922464 is fixed which + * will make it possible to parse all URI's off the main thread. + * + * getBaseDomainFromSchemeHost + * + * Get the base domain for aScheme and aHost. Otherwise identical to + * getBaseDomain(). + * + * @param aScheme + * The scheme to analyze. + * + * @param aAsciiHost + * The host to analyze. + * + * @return the base domain. + */ + AUTF8String getBaseDomainFromSchemeHost(in AUTF8String aScheme, + in AUTF8String aAsciiHost); + + /** + * getURIFromWindow + * + * Returns the URI associated with the script object principal for the + * window. + */ + nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getPrincipalFromWindow + * + * Returns the script object principal for the window. + */ + nsIPrincipal getPrincipalFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getTopWindowForChannel + * + * Returns the top-level window associated with the given channel. + */ + [noscript] + mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel, [optional] in nsIURI aURIBeingLoaded); + + /* + * Performs a full analysis of a channel. + * + * aChannel the input channel + * aNotify whether to send content blocking notifications if access control checks fail + * aURI optional URI to check for (the channel URI will be used instead if not provided) + * aRequireThirdPartyCheck a functor used to determine whether the load info requires third-party checks + */ + [noscript, notxpcom] + ThirdPartyAnalysisResult analyzeChannel(in nsIChannel aChannel, + in boolean aNotify, + [optional] in nsIURI aURI, + [optional] in RequireThirdPartyCheck aRequireThirdPartyCheck, + out uint32_t aRejectedReason); +}; + +%{ C++ +/** + * The mozIThirdPartyUtil implementation is an XPCOM service registered + * under the ContractID: + */ +#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1" +%} diff --git a/netwerk/base/mozurl/.gitignore b/netwerk/base/mozurl/.gitignore new file mode 100644 index 0000000000..4fffb2f89c --- /dev/null +++ b/netwerk/base/mozurl/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/netwerk/base/mozurl/Cargo.toml b/netwerk/base/mozurl/Cargo.toml new file mode 100644 index 0000000000..0eaccad0b8 --- /dev/null +++ b/netwerk/base/mozurl/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mozurl" +version = "0.0.1" +authors = ["Nika Layzell "] +license = "MPL-2.0" + +[dependencies] +url = "2.0" +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +xpcom = { path = "../../../xpcom/rust/xpcom" } +uuid = { version = "1.0", features = ["v4"] } diff --git a/netwerk/base/mozurl/MozURL.cpp b/netwerk/base/mozurl/MozURL.cpp new file mode 100644 index 0000000000..47a679164a --- /dev/null +++ b/netwerk/base/mozurl/MozURL.cpp @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/StaticPrefs_security.h" + +extern "C" { + +bool Gecko_StrictFileOriginPolicy() { + return mozilla::StaticPrefs::security_fileuri_strict_origin_policy(); +} +} diff --git a/netwerk/base/mozurl/MozURL.h b/netwerk/base/mozurl/MozURL.h new file mode 100644 index 0000000000..ddd91c1b0c --- /dev/null +++ b/netwerk/base/mozurl/MozURL.h @@ -0,0 +1,231 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozURL_h__ +#define mozURL_h__ + +#include "mozilla/net/MozURL_ffi.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" + +namespace mozilla { +namespace net { + +// This class provides a thread-safe, immutable URL parser. +// As long as there is RefPtr to the object, you may use it on any thread. +// The constructor is private. One can instantiate the object by +// calling the Init() method as such: +// +// RefPtr url; +// nsAutoCString href("http://example.com/path?query#ref"); +// nsresult rv = MozURL::Init(getter_AddRefs(url), href); +// if (NS_SUCCEEDED(rv)) { /* use url */ } +// +// When changing the URL is needed, you need to call the Mutate() method. +// This gives you a Mutator object, on which you can perform setter operations. +// Calling Finalize() on the Mutator will result in a new MozURL and a status +// code. If any of the setter operations failed, it will be reflected in the +// status code, and a null MozURL. +// +// Note: In the case of a domain name containing non-ascii characters, +// GetSpec and GetHostname will return the IDNA(punycode) version of the host. +// Also note that for now, MozURL only supports the UTF-8 charset. +// +// Implementor Note: This type is only a holder for methods in C++, and does not +// reflect the actual layout of the type. +class MozURL final { + public: + static nsresult Init(MozURL** aURL, const nsACString& aSpec, + const MozURL* aBaseURL = nullptr) { + return mozurl_new(aURL, &aSpec, aBaseURL); + } + + nsDependentCSubstring Spec() const { return mozurl_spec(this); } + nsDependentCSubstring Scheme() const { return mozurl_scheme(this); } + nsDependentCSubstring Username() const { return mozurl_username(this); } + nsDependentCSubstring Password() const { return mozurl_password(this); } + // Will return the hostname of URL. If the hostname is an IPv6 address, + // it will be enclosed in square brackets, such as `[::1]` + nsDependentCSubstring Host() const { return mozurl_host(this); } + // Will return the port number, if specified, or -1 + int32_t Port() const { return mozurl_port(this); } + int32_t RealPort() const { return mozurl_real_port(this); } + // If the URL's port number is equal to the default port, will only return the + // hostname, otherwise it will return a string of the form `{host}:{port}` + // See: https://url.spec.whatwg.org/#default-port + nsDependentCSubstring HostPort() const { return mozurl_host_port(this); } + nsDependentCSubstring FilePath() const { return mozurl_filepath(this); } + nsDependentCSubstring Path() const { return mozurl_path(this); } + nsDependentCSubstring Query() const { return mozurl_query(this); } + nsDependentCSubstring Ref() const { return mozurl_fragment(this); } + bool HasFragment() const { return mozurl_has_fragment(this); } + nsDependentCSubstring Directory() const { return mozurl_directory(this); } + nsDependentCSubstring PrePath() const { return mozurl_prepath(this); } + nsDependentCSubstring SpecNoRef() const { return mozurl_spec_no_ref(this); } + + // This matches the definition of origins and base domains in nsIPrincipal for + // almost all URIs (some rare file:// URIs don't match and it would be hard to + // fix them). It definitely matches nsIPrincipal for URIs used in quota + // manager and there are checks in quota manager and its clients that prevent + // different definitions (see QuotaManager::IsPrincipalInfoValid). + // See also TestMozURL.cpp which enumerates a huge pile of URIs and checks + // that origin and base domain definitions are in sync. + void Origin(nsACString& aOrigin) const { mozurl_origin(this, &aOrigin); } + nsresult BaseDomain(nsACString& aBaseDomain) const { + return mozurl_base_domain(this, &aBaseDomain); + } + + nsresult GetCommonBase(const MozURL* aOther, MozURL** aCommon) const { + return mozurl_common_base(this, aOther, aCommon); + } + nsresult GetRelative(const MozURL* aOther, nsACString* aRelative) const { + return mozurl_relative(this, aOther, aRelative); + } + + size_t SizeOf() { return mozurl_sizeof(this); } + + class Mutator { + public: + // Calling this method will result in the creation of a new MozURL that + // adopts the mutator's mURL. + // If any of the setters failed with an error code, that error code will be + // returned here. It will also return an error code if Finalize is called + // more than once on the Mutator. + nsresult Finalize(MozURL** aURL) { + nsresult rv = GetStatus(); + if (NS_SUCCEEDED(rv)) { + mURL.forget(aURL); + } else { + *aURL = nullptr; + } + return rv; + } + + // These setter methods will return a reference to `this` so that you may + // chain setter operations as such: + // + // RefPtr url2; + // nsresult rv = url->Mutate().SetHostname("newhost"_ns) + // .SetFilePath("new/file/path"_ns) + // .Finalize(getter_AddRefs(url2)); + // if (NS_SUCCEEDED(rv)) { /* use url2 */ } + Mutator& SetScheme(const nsACString& aScheme) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_scheme(mURL, &aScheme); + } + return *this; + } + Mutator& SetUsername(const nsACString& aUser) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_username(mURL, &aUser); + } + return *this; + } + Mutator& SetPassword(const nsACString& aPassword) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_password(mURL, &aPassword); + } + return *this; + } + Mutator& SetHostname(const nsACString& aHost) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_hostname(mURL, &aHost); + } + return *this; + } + Mutator& SetHostPort(const nsACString& aHostPort) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_host_port(mURL, &aHostPort); + } + return *this; + } + Mutator& SetFilePath(const nsACString& aPath) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_pathname(mURL, &aPath); + } + return *this; + } + Mutator& SetQuery(const nsACString& aQuery) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_query(mURL, &aQuery); + } + return *this; + } + Mutator& SetRef(const nsACString& aRef) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_fragment(mURL, &aRef); + } + return *this; + } + Mutator& SetPort(int32_t aPort) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_port_no(mURL, aPort); + } + return *this; + } + + // This method returns the status code of the setter operations. + // If any of the setters failed, it will return the code of the first error + // that occured. If none of the setters failed, it will return NS_OK. + // This method is useful to avoid doing expensive operations when the result + // would not be used because an error occurred. For example: + // + // RefPtr url2; + // MozURL::Mutator mut = url->Mutate(); + // mut.SetScheme("!@#$"); // this would fail + // if (NS_SUCCEDED(mut.GetStatus())) { + // nsAutoCString host(ExpensiveComputing()); + // rv = mut.SetHostname(host).Finalize(getter_AddRefs(url2)); + // } + // if (NS_SUCCEEDED(rv)) { /* use url2 */ } + nsresult GetStatus() { return mURL ? mStatus : NS_ERROR_NOT_AVAILABLE; } + + static Result FromSpec( + const nsACString& aSpec, const MozURL* aBaseURL = nullptr) { + Mutator m = Mutator(aSpec, aBaseURL); + if (m.mURL) { + MOZ_ASSERT(NS_SUCCEEDED(m.mStatus)); + return m; + } + + MOZ_ASSERT(NS_FAILED(m.mStatus)); + return Err(m.mStatus); + } + + private: + explicit Mutator(MozURL* aUrl) : mStatus(NS_OK) { + mozurl_clone(aUrl, getter_AddRefs(mURL)); + } + + // This is only used to create a mutator from a string without cloning it + // so we avoid a pointless copy in FromSpec. It is important that we + // check the value of mURL afterwards. + explicit Mutator(const nsACString& aSpec, + const MozURL* aBaseURL = nullptr) { + mStatus = mozurl_new(getter_AddRefs(mURL), &aSpec, aBaseURL); + } + RefPtr mURL; + nsresult mStatus; + friend class MozURL; + }; + + Mutator Mutate() { return Mutator(this); } + + // AddRef and Release are non-virtual on this type, and always call into rust. + void AddRef() { mozurl_addref(this); } + void Release() { mozurl_release(this); } + + private: + // Make it a compile time error for C++ code to ever create, destruct, or copy + // MozURL objects. All of these operations will be performed by rust. + MozURL(); /* never defined */ + ~MozURL(); /* never defined */ + MozURL(const MozURL&) = delete; + MozURL& operator=(const MozURL&) = delete; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozURL_h__ diff --git a/netwerk/base/mozurl/cbindgen.toml b/netwerk/base/mozurl/cbindgen.toml new file mode 100644 index 0000000000..bb5fdbd08f --- /dev/null +++ b/netwerk/base/mozurl/cbindgen.toml @@ -0,0 +1,61 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MozURL type is implemented in Rust code, and uses extern "C" FFI calls to + * operate on the internal data structure. This file contains C declarations of + * these files. + + * WARNING: DO NOT CALL ANY OF THESE FUNCTIONS. USE |MozURL| INSTEAD! */ + """ +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ + +namespace mozilla { +namespace net { +class MozURL; +} // namespace net +} // namespace mozilla + +extern "C" { + +// FFI-compatible string slice struct used internally by MozURL. +// Coerces to nsDependentCSubstring. +struct MozURLSpecSlice { + char* mData; + uint32_t mLen; + + operator nsDependentCSubstring() { + return nsDependentCSubstring(mData, mLen); + } +}; + +nsresult mozurl_new(mozilla::net::MozURL** aResult, const nsACString* aSpec, + /* optional */ const mozilla::net::MozURL* aBase); + +void mozurl_clone(const mozilla::net::MozURL* aThis, + mozilla::net::MozURL** aResult); + +nsresult mozurl_common_base(const mozilla::net::MozURL* aUrl1, + const mozilla::net::MozURL* aUrl2, + mozilla::net::MozURL** aResult); +} + +""" + +include_guard = "mozilla_net_MozURL_ffi_h" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = [] +includes = ["nsError.h", "nsString.h"] + +[export] +exclude = ["Gecko_StrictFileOriginPolicy", "MozURL", "SpecSlice", "mozurl_new", "mozurl_clone", "mozurl_common_base"] +item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"] + + +[export.rename] +"SpecSlice" = "MozURLSpecSlice" +"MozURL" = "mozilla::net::MozURL" diff --git a/netwerk/base/mozurl/moz.build b/netwerk/base/mozurl/moz.build new file mode 100644 index 0000000000..0a4e522b5a --- /dev/null +++ b/netwerk/base/mozurl/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.net += [ + "MozURL.h", +] + +SOURCES += [ + "MozURL.cpp", +] + +FINAL_LIBRARY = "xul" + +if CONFIG["COMPILE_ENVIRONMENT"]: + CbindgenHeader("MozURL_ffi.h", inputs=["/netwerk/base/mozurl"]) + + EXPORTS.mozilla.net += [ + "!MozURL_ffi.h", + ] diff --git a/netwerk/base/mozurl/src/lib.rs b/netwerk/base/mozurl/src/lib.rs new file mode 100644 index 0000000000..7b73311e73 --- /dev/null +++ b/netwerk/base/mozurl/src/lib.rs @@ -0,0 +1,564 @@ +/* -*- Mode: rust; rust-indent-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate url; +use url::quirks; +use url::{ParseOptions, Position, Url}; + +extern crate nsstring; +use nsstring::{nsACString, nsCString}; + +extern crate nserror; +use nserror::*; + +extern crate xpcom; +use xpcom::interfaces::mozIThirdPartyUtil; +use xpcom::{AtomicRefcnt, RefCounted, RefPtr}; + +extern crate uuid; +use uuid::Uuid; + +use std::fmt::Write; +use std::marker::PhantomData; +use std::mem; +use std::ops; +use std::ptr; +use std::str; + +extern "C" { + fn Gecko_StrictFileOriginPolicy() -> bool; +} + +/// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise, +/// returns NS_ERROR_MALFORMED_URI. +macro_rules! try_or_malformed { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(_) => return NS_ERROR_MALFORMED_URI, + } + }; +} + +fn parser<'a>() -> ParseOptions<'a> { + Url::options() +} + +fn default_port(scheme: &str) -> Option { + 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, + } +} + +/// A slice into the backing string. This type is only valid as long as the +/// MozURL which it was pulled from is valid. In C++, this type implicitly +/// converts to a nsDependentCString, and is an implementation detail. +/// +/// This type exists because, unlike &str, this type is safe to return over FFI. +#[repr(C)] +pub struct SpecSlice<'a> { + data: *const u8, + len: u32, + _marker: PhantomData<&'a [u8]>, +} + +impl<'a> From<&'a str> for SpecSlice<'a> { + fn from(s: &'a str) -> SpecSlice<'a> { + assert!(s.len() < u32::max_value() as usize); + SpecSlice { + data: s.as_ptr(), + len: s.len() as u32, + _marker: PhantomData, + } + } +} + +/// The MozURL reference-counted threadsafe URL type. This type intentionally +/// implements no XPCOM interfaces, and all method calls are non-virtual. +#[repr(C)] +pub struct MozURL { + pub url: Url, + refcnt: AtomicRefcnt, +} + +impl MozURL { + pub fn from_url(url: Url) -> RefPtr { + // Actually allocate the URL on the heap. This is the only place we actually + // create a MozURL, other than in clone(). + unsafe { + RefPtr::from_raw(Box::into_raw(Box::new(MozURL { + url: url, + refcnt: AtomicRefcnt::new(), + }))) + .unwrap() + } + } +} + +impl ops::Deref for MozURL { + type Target = Url; + fn deref(&self) -> &Url { + &self.url + } +} +impl ops::DerefMut for MozURL { + fn deref_mut(&mut self) -> &mut Url { + &mut self.url + } +} + +// Memory Management for MozURL +#[no_mangle] +pub unsafe extern "C" fn mozurl_addref(url: &MozURL) { + url.refcnt.inc(); +} + +#[no_mangle] +pub unsafe extern "C" fn mozurl_release(url: &MozURL) { + let rc = url.refcnt.dec(); + if rc == 0 { + mem::drop(Box::from_raw(url as *const MozURL as *mut MozURL)); + } +} + +// xpcom::RefPtr support +unsafe impl RefCounted for MozURL { + unsafe fn addref(&self) { + mozurl_addref(self); + } + unsafe fn release(&self) { + mozurl_release(self); + } +} + +// Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it +// into url. +#[no_mangle] +pub extern "C" fn mozurl_new( + result: &mut *const MozURL, + spec: &nsACString, + base: Option<&MozURL>, +) -> nsresult { + *result = ptr::null_mut(); + + let spec = try_or_malformed!(str::from_utf8(spec)); + let url = if let Some(base) = base { + try_or_malformed!(base.url.join(spec)) + } else { + try_or_malformed!(parser().parse(spec)) + }; + + MozURL::from_url(url).forget(result); + NS_OK +} + +/// Allocate a new MozURL object which is a clone of the original, and store a +/// pointer to it into newurl. +#[no_mangle] +pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) { + MozURL::from_url(url.url.clone()).forget(newurl); +} + +#[no_mangle] +pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice { + url.as_ref().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice { + url.scheme().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice { + if url.cannot_be_a_base() { + "".into() + } else { + url.username().into() + } +} + +#[no_mangle] +pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice { + url.password().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice { + url.host_str().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_port(url: &MozURL) -> i32 { + // NOTE: Gecko uses -1 to represent the default port. + url.port().map(|p| p as i32).unwrap_or(-1) +} + +#[no_mangle] +pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 { + url.port() + .or_else(|| default_port(url.scheme())) + .map(|p| p as i32) + .unwrap_or(-1) +} + +#[no_mangle] +pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice { + (&url[Position::BeforeHost..Position::BeforePath]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice { + url.path().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice { + (&url[Position::BeforePath..]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice { + url.query().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice { + url.fragment().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice { + (&url[..Position::AfterQuery]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool { + url.fragment().is_some() +} + +#[no_mangle] +pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice { + if let Some(position) = url.path().rfind('/') { + url.path()[..position + 1].into() + } else { + url.path().into() + } +} + +#[no_mangle] +pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice { + (&url[..Position::BeforePath]).into() +} + +fn get_origin(url: &MozURL) -> Option { + match url.scheme() { + "blob" | "ftp" | "http" | "https" | "ws" | "wss" => { + Some(url.origin().ascii_serialization()) + } + "indexeddb" | "moz-extension" | "resource" => { + let host = url.host_str().unwrap_or(""); + + let port = url.port().or_else(|| default_port(url.scheme())); + + if port == default_port(url.scheme()) { + Some(format!("{}://{}", url.scheme(), host)) + } else { + Some(format!("{}://{}:{}", url.scheme(), host, port.unwrap())) + } + } + "file" => { + if unsafe { Gecko_StrictFileOriginPolicy() } { + Some(url[..Position::AfterPath].to_owned()) + } else { + Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned()) + } + } + "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()), + _ => None, + } +} + +#[no_mangle] +pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) { + let origin_str = if !url.as_ref().starts_with("about:blank") { + get_origin(url) + } else { + None + }; + + let origin_str = origin_str.unwrap_or_else(|| { + // nsIPrincipal stores the uuid, so the same uuid is returned everytime. + // We can't do that for MozURL because it can be used across threads. + // Storing uuid would mutate the object which would cause races between + // threads. + format!("moz-nullprincipal:{{{}}}", Uuid::new_v4()) + }); + + // NOTE: Try to re-use the allocation we got from rust-url, and transfer + // ownership of the buffer to C++. + let mut o = nsCString::from(origin_str); + origin.take_from(&mut o); +} + +fn get_base_domain(url: &MozURL) -> Result, nsresult> { + match url.scheme() { + "ftp" | "http" | "https" | "moz-extension" | "resource" => { + let third_party_util: RefPtr = + xpcom::components::ThirdPartyUtil::service() + .map_err(|_| NS_ERROR_ILLEGAL_DURING_SHUTDOWN)?; + + let scheme = nsCString::from(url.scheme()); + + let mut host_str = url.host_str().unwrap_or(""); + + if host_str.starts_with('[') && host_str.ends_with(']') { + host_str = &host_str[1..host_str.len() - 1]; + } + + let host = nsCString::from(host_str); + + unsafe { + let mut string = nsCString::new(); + third_party_util + .GetBaseDomainFromSchemeHost(&*scheme, &*host, &mut *string) + .to_result()?; + + // We know that GetBaseDomainFromSchemeHost returns AUTF8String, so just + // use unwrap(). + Ok(Some(String::from_utf8(string.to_vec()).unwrap())) + } + } + "ws" | "wss" => Ok(Some(url.as_ref().to_owned())), + "file" => { + if unsafe { Gecko_StrictFileOriginPolicy() } { + Ok(Some(url.path().to_owned())) + } else { + Ok(Some("UNIVERSAL_FILE_URI_ORIGIN".to_owned())) + } + } + "about" | "moz-safe-about" | "indexeddb" => Ok(Some(url.as_ref().to_owned())), + _ => Ok(None), + } +} + +#[no_mangle] +pub extern "C" fn mozurl_base_domain(url: &MozURL, base_domain: &mut nsACString) -> nsresult { + let base_domain_str = if !url.as_ref().starts_with("about:blank") { + match get_base_domain(url) { + Ok(domain) => domain, + Err(rv) => return rv, + } + } else { + None + }; + + let base_domain_str = base_domain_str.unwrap_or_else(|| { + // See the comment in mozurl_origin about why we return a new uuid for + // "moz-nullprincipals". + format!("{{{}}}", Uuid::new_v4()) + }); + + let mut bd = nsCString::from(base_domain_str); + base_domain.take_from(&mut bd); + + NS_OK +} + +// Helper macro for debug asserting that we're the only reference to MozURL. +macro_rules! debug_assert_mut { + ($e:expr) => { + debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!"); + }; +} + +#[no_mangle] +pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult { + debug_assert_mut!(url); + let scheme = try_or_malformed!(str::from_utf8(scheme)); + try_or_malformed!(quirks::set_protocol(url, scheme)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult { + debug_assert_mut!(url); + let username = try_or_malformed!(str::from_utf8(username)); + try_or_malformed!(quirks::set_username(url, username)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult { + debug_assert_mut!(url); + let password = try_or_malformed!(str::from_utf8(password)); + try_or_malformed!(quirks::set_password(url, password)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult { + debug_assert_mut!(url); + let hostport = try_or_malformed!(str::from_utf8(hostport)); + try_or_malformed!(quirks::set_host(url, hostport)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult { + debug_assert_mut!(url); + let host = try_or_malformed!(str::from_utf8(host)); + try_or_malformed!(quirks::set_hostname(url, host)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult { + debug_assert_mut!(url); + if url.cannot_be_a_base() { + return NS_ERROR_MALFORMED_URI; + } + + let port = match new_port { + new if new < 0 || u16::max_value() as i32 <= new => None, + new if Some(new as u16) == default_port(url.scheme()) => None, + new => Some(new as u16), + }; + try_or_malformed!(url.set_port(port)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult { + debug_assert_mut!(url); + let path = try_or_malformed!(str::from_utf8(path)); + quirks::set_pathname(url, path); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult { + debug_assert_mut!(url); + let query = try_or_malformed!(str::from_utf8(query)); + quirks::set_search(url, query); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult { + debug_assert_mut!(url); + let fragment = try_or_malformed!(str::from_utf8(fragment)); + quirks::set_hash(url, fragment); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize { + debug_assert_mut!(url); + mem::size_of::() + url.as_str().len() +} + +#[no_mangle] +pub extern "C" fn mozurl_common_base( + url1: &MozURL, + url2: &MozURL, + result: &mut *const MozURL, +) -> nsresult { + *result = ptr::null(); + if url1.url == url2.url { + RefPtr::new(url1).forget(result); + return NS_OK; + } + + if url1.scheme() != url2.scheme() + || url1.host() != url2.host() + || url1.username() != url2.username() + || url1.password() != url2.password() + || url1.port() != url2.port() + { + return NS_OK; + } + + match (url1.path_segments(), url2.path_segments()) { + (Some(path1), Some(path2)) => { + // Take the shared prefix of path segments + let mut url = url1.url.clone(); + if let Ok(mut segs) = url.path_segments_mut() { + segs.clear(); + segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0)); + } else { + return NS_OK; + } + + MozURL::from_url(url).forget(result); + NS_OK + } + _ => NS_OK, + } +} + +#[no_mangle] +pub extern "C" fn mozurl_relative( + url1: &MozURL, + url2: &MozURL, + result: &mut nsACString, +) -> nsresult { + if url1.url == url2.url { + result.truncate(); + return NS_OK; + } + + if url1.scheme() != url2.scheme() + || url1.host() != url2.host() + || url1.username() != url2.username() + || url1.password() != url2.password() + || url1.port() != url2.port() + { + result.assign(url2.as_ref()); + return NS_OK; + } + + match (url1.path_segments(), url2.path_segments()) { + (Some(mut path1), Some(mut path2)) => { + // Exhaust the part of the iterators that match + while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) { + if p1 != p2 { + break; + } + } + + result.truncate(); + for _ in path1 { + result.append("../"); + } + for p2 in path2 { + result.append(p2); + result.append("/"); + } + } + _ => { + result.assign(url2.as_ref()); + } + } + NS_OK +} + +/// This type is used by nsStandardURL +#[no_mangle] +pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult { + let ip6 = try_or_malformed!(str::from_utf8(input)); + let host = try_or_malformed!(url::Host::parse(ip6)); + let _ = write!(addr, "{}", host); + NS_OK +} diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h new file mode 100644 index 0000000000..258cc5cb58 --- /dev/null +++ b/netwerk/base/netCore.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __netCore_h__ +#define __netCore_h__ + +#include "nsError.h" + +// Where most necko status messages come from: +#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties" + +#endif // __netCore_h__ diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h new file mode 100644 index 0000000000..c68da7c359 --- /dev/null +++ b/netwerk/base/nsASocketHandler.h @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsASocketHandler_h__ +#define nsASocketHandler_h__ + +#include "nsError.h" +#include "nsINetAddr.h" +#include "nsISupports.h" +#include "prio.h" + +// socket handler used by nsISocketTransportService. +// methods are only called on the socket thread. + +class nsASocketHandler : public nsISupports { + public: + nsASocketHandler() = default; + + // + // this condition variable will be checked to determine if the socket + // handler should be detached. it must only be accessed on the socket + // thread. + // + nsresult mCondition{NS_OK}; + + // + // these flags can only be modified on the socket transport thread. + // the socket transport service will check these flags before calling + // PR_Poll. + // + uint16_t mPollFlags{0}; + + // + // this value specifies the maximum amount of time in seconds that may be + // spent waiting for activity on this socket. if this timeout is reached, + // then OnSocketReady will be called with outFlags = -1. + // + // the default value for this member is UINT16_MAX, which disables the + // timeout error checking. (i.e., a timeout value of UINT16_MAX is + // never reached.) + // + uint16_t mPollTimeout{UINT16_MAX}; + + bool mIsPrivate{false}; + + // + // called to service a socket + // + // params: + // socketRef - socket identifier + // fd - socket file descriptor + // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns + // or -1 if a timeout occurred + // + virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) = 0; + + // + // called when a socket is no longer under the control of the socket + // transport service. the socket handler may close the socket at this + // point. after this call returns, the handler will no longer be owned + // by the socket transport service. + // + virtual void OnSocketDetached(PRFileDesc* fd) = 0; + + // + // called to determine if the socket is for a local peer. + // when used for server sockets, indicates if it only accepts local + // connections. + // + virtual void IsLocal(bool* aIsLocal) = 0; + + // + // called to determine if this socket should not be terminated when Gecko + // is turned offline. This is mostly useful for the debugging server + // socket. + // + virtual void KeepWhenOffline(bool* aKeepWhenOffline) { + *aKeepWhenOffline = false; + } + + // + // called when global pref for keepalive has changed. + // + virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) {} + + // + // called to determine the NetAddr. addr can only be assumed to be initialized + // when NS_OK is returned + // + virtual nsresult GetRemoteAddr(mozilla::net::NetAddr* addr) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // + // returns the number of bytes sent/transmitted over the socket + // + virtual uint64_t ByteCountSent() = 0; + virtual uint64_t ByteCountReceived() = 0; +}; + +#endif // !nsASocketHandler_h__ diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp new file mode 100644 index 0000000000..7a10d4d3f0 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" + +#include "nsIOService.h" +#include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadInfo.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gRedirectLog("nsRedirect"); +#undef LOG +#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, nsIAsyncVerifyRedirectCallback, + nsIRunnable, nsINamed) + +class nsAsyncVerifyRedirectCallbackEvent : public Runnable { + public: + nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback* cb, + nsresult result) + : Runnable("nsAsyncVerifyRedirectCallbackEvent"), + mCallback(cb), + mResult(result) {} + + NS_IMETHOD Run() override { + LOG( + ("nsAsyncVerifyRedirectCallbackEvent::Run() " + "callback to %p with result %" PRIx32, + mCallback.get(), static_cast(mResult))); + (void)mCallback->OnRedirectVerifyCallback(mResult); + return NS_OK; + } + + private: + nsCOMPtr mCallback; + nsresult mResult; +}; + +nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() { + NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, + "Did not receive all required callbacks!"); +} + +nsresult nsAsyncRedirectVerifyHelper::Init( + nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags, + nsIEventTarget* mainThreadEventTarget, bool synchronize) { + LOG( + ("nsAsyncRedirectVerifyHelper::Init() " + "oldChan=%p newChan=%p", + oldChan, newChan)); + mOldChan = oldChan; + mNewChan = newChan; + mFlags = flags; + mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget + ? mainThreadEventTarget + : GetCurrentSerialEventTarget(); + + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr loadInfo = oldChan->LoadInfo(); + if (loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + + if (synchronize) mWaitingForRedirectCallback = true; + + nsCOMPtr runnable = this; + nsresult rv; + rv = mainThreadEventTarget + ? mainThreadEventTarget->Dispatch(runnable.forget()) + : GetMainThreadSerialEventTarget()->Dispatch(runnable.forget()); + NS_ENSURE_SUCCESS(rv, rv); + + if (synchronize) { + if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns, + [&]() { return !mWaitingForRedirectCallback; })) { + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) { + LOG( + ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " + "result=%" PRIx32 " expectedCBs=%u mResult=%" PRIx32, + static_cast(result), mExpectedCallbacks, + static_cast(mResult))); + + MOZ_DIAGNOSTIC_ASSERT( + mExpectedCallbacks > 0, + "OnRedirectVerifyCallback called more times than expected"); + if (mExpectedCallbacks <= 0) { + return NS_ERROR_UNEXPECTED; + } + + --mExpectedCallbacks; + + // If response indicates failure we may call back immediately + if (NS_FAILED(result)) { + // We chose to store the first failure-value (as opposed to the last) + if (NS_SUCCEEDED(mResult)) mResult = result; + + // If InitCallback() has been called, just invoke the callback and + // return. Otherwise it will be invoked from InitCallback() + if (mCallbackInitiated) { + ExplicitCallback(mResult); + return NS_OK; + } + } + + // If the expected-counter is in balance and InitCallback() was called, all + // sinks have agreed that the redirect is ok and we can invoke our callback + if (mCallbackInitiated && mExpectedCallbacks == 0) { + ExplicitCallback(mResult); + } + + return NS_OK; +} + +nsresult nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect( + nsIChannelEventSink* sink, nsIChannel* oldChannel, nsIChannel* newChannel, + uint32_t flags) { + LOG( + ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " + "sink=%p expectedCBs=%u mResult=%" PRIx32, + sink, mExpectedCallbacks, static_cast(mResult))); + + ++mExpectedCallbacks; + + if (IsOldChannelCanceled()) { + LOG( + (" old channel has been canceled, cancel the redirect by " + "emulating OnRedirectVerifyCallback...")); + (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED); + return NS_BINDING_ABORTED; + } + + nsresult rv = + sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); + + LOG((" result=%" PRIx32 " expectedCBs=%u", static_cast(rv), + mExpectedCallbacks)); + + // If the sink returns failure from this call the redirect is vetoed. We + // emulate a callback from the sink in this case in order to perform all + // the necessary logic. + if (NS_FAILED(rv)) { + LOG((" emulating OnRedirectVerifyCallback...")); + (void)OnRedirectVerifyCallback(rv); + } + + return rv; // Return the actual status since our caller may need it +} + +void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "result=%" PRIx32 + " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32, + static_cast(result), mExpectedCallbacks, mCallbackInitiated, + static_cast(mResult))); + + nsCOMPtr callback( + do_QueryInterface(mOldChan)); + + if (!callback || !mCallbackEventTarget) { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "callback=%p mCallbackEventTarget=%p", + callback.get(), mCallbackEventTarget.get())); + return; + } + + mCallbackInitiated = false; // reset to ensure only one callback + mWaitingForRedirectCallback = false; + + // Now, dispatch the callback on the event-target which called Init() + nsCOMPtr event = + new nsAsyncVerifyRedirectCallbackEvent(callback, result); + if (!event) { + NS_WARNING( + "nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed creating callback event!"); + return; + } + nsresult rv = mCallbackEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING( + "nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed dispatching callback event!"); + } else { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "dispatched callback event=%p", + event.get())); + } +} + +void nsAsyncRedirectVerifyHelper::InitCallback() { + LOG( + ("nsAsyncRedirectVerifyHelper::InitCallback() " + "expectedCBs=%d mResult=%" PRIx32, + mExpectedCallbacks, static_cast(mResult))); + + mCallbackInitiated = true; + + // Invoke the callback if we are done + if (mExpectedCallbacks == 0) ExplicitCallback(mResult); +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::GetName(nsACString& aName) { + aName.AssignLiteral("nsAsyncRedirectVerifyHelper"); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::Run() { + /* If the channel got canceled after it fired AsyncOnChannelRedirect + * and before we got here, mostly because docloader load has been canceled, + * we must completely ignore this notification and prevent any further + * notification. + */ + if (IsOldChannelCanceled()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + + // First, the global observer + NS_ASSERTION(gIOService, "Must have an IO service at this point"); + LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); + nsresult rv = + gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, mFlags, this); + if (NS_FAILED(rv)) { + ExplicitCallback(rv); + return NS_OK; + } + + // Now, the per-channel observers + nsCOMPtr sink; + NS_QueryNotificationCallbacks(mOldChan, sink); + if (sink) { + LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); + rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); + } + + // All invocations to AsyncOnChannelRedirect has been done - call + // InitCallback() to flag this + InitCallback(); + return NS_OK; +} + +bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() { + if (!mOldChan) { + return false; + } + bool canceled; + nsresult rv = mOldChan->GetCanceled(&canceled); + return NS_SUCCEEDED(rv) && canceled; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h new file mode 100644 index 0000000000..3e1afa8c69 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncRedirectVerifyHelper_h +#define nsAsyncRedirectVerifyHelper_h + +#include "nsIRunnable.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsINamed.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +/** + * This class simplifies call of OnChannelRedirect of IOService and + * the sink bound with the channel being redirected while the result of + * redirect decision is returned through the callback. + */ +class nsAsyncRedirectVerifyHelper final + : public nsIRunnable, + public nsINamed, + public nsIAsyncVerifyRedirectCallback { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSINAMED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + public: + nsAsyncRedirectVerifyHelper() = default; + + /* + * Calls AsyncOnChannelRedirect() on the given sink with the given + * channels and flags. Keeps track of number of async callbacks to expect. + */ + nsresult DelegateOnChannelRedirect(nsIChannelEventSink* sink, + nsIChannel* oldChannel, + nsIChannel* newChannel, uint32_t flags); + + /** + * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel + * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect + * decision is passed through this interface back to the oldChannel. + * + * @param oldChan + * channel being redirected, MUST implement + * nsIAsyncVerifyRedirectCallback + * @param newChan + * target of the redirect channel + * @param flags + * redirect flags + * @param mainThreadEventTarget + * a labeled event target for dispatching runnables + * @param synchronize + * set to TRUE if you want the Init method wait synchronously for + * all redirect callbacks + */ + nsresult Init(nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags, + nsIEventTarget* mainThreadEventTarget, + bool synchronize = false); + + protected: + nsCOMPtr mOldChan; + nsCOMPtr mNewChan; + uint32_t mFlags{0}; + bool mWaitingForRedirectCallback{false}; + nsCOMPtr mCallbackEventTarget; + bool mCallbackInitiated{false}; + int32_t mExpectedCallbacks{0}; + nsresult mResult{NS_OK}; // value passed to callback + + void InitCallback(); + + /** + * Calls back to |oldChan| as described in Init() + */ + void ExplicitCallback(nsresult result); + + private: + ~nsAsyncRedirectVerifyHelper(); + + bool IsOldChannelCanceled(); +}; + +/* + * Helper to make the call-stack handle some control-flow for us + */ +class nsAsyncRedirectAutoCallback { + public: + explicit nsAsyncRedirectAutoCallback( + nsIAsyncVerifyRedirectCallback* aCallback) + : mCallback(aCallback) { + mResult = NS_OK; + } + ~nsAsyncRedirectAutoCallback() { + if (mCallback) mCallback->OnRedirectVerifyCallback(mResult); + } + /* + * Call this is you want it to call back with a different result-code + */ + void SetResult(nsresult aRes) { mResult = aRes; } + /* + * Call this is you want to avoid the callback + */ + void DontCallback() { mCallback = nullptr; } + + private: + nsIAsyncVerifyRedirectCallback* mCallback; + nsresult mResult; +}; + +} // namespace net +} // namespace mozilla +#endif diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp new file mode 100644 index 0000000000..de8363d17d --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.cpp @@ -0,0 +1,405 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAsyncStreamCopier.h" +#include "nsComponentManagerUtils.h" +#include "nsIOService.h" +#include "nsIEventTarget.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsIBufferedStreams.h" +#include "nsIRequestObserver.h" +#include "mozilla/Logging.h" + +using namespace mozilla; +using namespace mozilla::net; + +#undef LOG +// +// MOZ_LOG=nsStreamCopier:5 +// +static LazyLogModule gStreamCopierLog("nsStreamCopier"); +#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args) + +/** + * An event used to perform initialization off the main thread. + */ +class AsyncApplyBufferingPolicyEvent final : public Runnable { + public: + /** + * @param aCopier + * The nsAsyncStreamCopier requesting the information. + */ + explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier) + : mozilla::Runnable("AsyncApplyBufferingPolicyEvent"), + mCopier(aCopier), + mTarget(GetCurrentSerialEventTarget()) {} + + NS_IMETHOD Run() override { + nsresult rv = mCopier->ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + return NS_OK; + } + + rv = mTarget->Dispatch( + NewRunnableMethod("nsAsyncStreamCopier::AsyncCopyInternal", mCopier, + &nsAsyncStreamCopier::AsyncCopyInternal), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + } + return NS_OK; + } + + private: + RefPtr mCopier; + nsCOMPtr mTarget; +}; + +//----------------------------------------------------------------------------- + +nsAsyncStreamCopier::nsAsyncStreamCopier() + : mChunkSize(nsIOService::gDefaultSegmentSize) { + LOG(("Creating nsAsyncStreamCopier @%p\n", this)); +} + +nsAsyncStreamCopier::~nsAsyncStreamCopier() { + LOG(("Destroying nsAsyncStreamCopier @%p\n", this)); +} + +bool nsAsyncStreamCopier::IsComplete(nsresult* status) { + MutexAutoLock lock(mLock); + if (status) *status = mStatus; + return !mIsPending; +} + +nsIRequest* nsAsyncStreamCopier::AsRequest() { + return static_cast(static_cast(this)); +} + +void nsAsyncStreamCopier::Complete(nsresult status) { + LOG(("nsAsyncStreamCopier::Complete [this=%p status=%" PRIx32 "]\n", this, + static_cast(status))); + + nsCOMPtr observer; + nsCOMPtr ctx; + { + MutexAutoLock lock(mLock); + mCopierCtx = nullptr; + + if (mIsPending) { + mIsPending = false; + mStatus = status; + + // setup OnStopRequest callback and release references... + observer = mObserver; + mObserver = nullptr; + } + } + + if (observer) { + LOG((" calling OnStopRequest [status=%" PRIx32 "]\n", + static_cast(status))); + observer->OnStopRequest(AsRequest(), status); + } +} + +void nsAsyncStreamCopier::OnAsyncCopyComplete(void* closure, nsresult status) { + // AddRef'd in AsyncCopy. Will be released at the end of the method. + RefPtr self = dont_AddRef((nsAsyncStreamCopier*)closure); + self->Complete(status); +} + +//----------------------------------------------------------------------------- +// nsISupports + +// We cannot use simply NS_IMPL_ISUPPORTSx as both +// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest + +NS_IMPL_ADDREF(nsAsyncStreamCopier) +NS_IMPL_RELEASE(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, + nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, + nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +//----------------------------------------------------------------------------- +// nsIRequest + +NS_IMETHODIMP +nsAsyncStreamCopier::GetName(nsACString& name) { + name.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::IsPending(bool* result) { + *result = !IsComplete(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetStatus(nsresult* status) { + IsComplete(status); + return NS_OK; +} + +NS_IMETHODIMP nsAsyncStreamCopier::SetCanceledReason( + const nsACString& aReason) { + return nsIAsyncStreamCopier::SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsAsyncStreamCopier::GetCanceledReason(nsACString& aReason) { + return nsIAsyncStreamCopier::GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsAsyncStreamCopier::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return nsIAsyncStreamCopier::CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Cancel(nsresult status) { + nsCOMPtr copierCtx; + { + MutexAutoLock lock(mLock); + if (!mIsPending) { + return NS_OK; + } + copierCtx.swap(mCopierCtx); + } + + if (NS_SUCCEEDED(status)) { + NS_WARNING("cancel with non-failure status code"); + status = NS_BASE_STREAM_CLOSED; + } + + if (copierCtx) NS_CancelAsyncCopy(copierCtx, status); + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Suspend() { + MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Suspend"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Resume() { + MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Resume"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +nsAsyncStreamCopier::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return nsIAsyncStreamCopier::GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return nsIAsyncStreamCopier::SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +// Can't be accessed by multiple threads yet +nsresult nsAsyncStreamCopier::InitInternal( + nsIInputStream* source, nsIOutputStream* sink, nsIEventTarget* target, + uint32_t chunkSize, bool closeSource, + bool closeSink) MOZ_NO_THREAD_SAFETY_ANALYSIS { + NS_ASSERTION(!mSource && !mSink, "Init() called more than once"); + if (chunkSize == 0) { + chunkSize = nsIOService::gDefaultSegmentSize; + } + mChunkSize = chunkSize; + + mSource = source; + mSink = sink; + mCloseSource = closeSource; + mCloseSink = closeSink; + + if (target) { + mTarget = target; + } else { + nsresult rv; + mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, bool sourceBuffered, + bool sinkBuffered, uint32_t chunkSize, + bool closeSource, bool closeSink) { + NS_ASSERTION(sourceBuffered || sinkBuffered, + "at least one stream must be buffered"); + mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS + : NS_ASYNCCOPY_VIA_WRITESEGMENTS; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, uint32_t chunkSize, + bool closeSource, bool closeSink) { + mShouldSniffBuffering = true; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +/** + * Detect whether the input or the output stream is buffered, + * bufferize one of them if neither is buffered. + */ +nsresult nsAsyncStreamCopier::ApplyBufferingPolicy() { + // This function causes I/O, it must not be executed on the main + // thread. + MOZ_ASSERT(!NS_IsMainThread()); + + if (NS_OutputStreamIsBuffered(mSink)) { + // Sink is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + return NS_OK; + } + if (NS_InputStreamIsBuffered(mSource)) { + // Source is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_READSEGMENTS; + return NS_OK; + } + + // No buffering, let's buffer the sink + nsresult rv; + nsCOMPtr sink = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = sink->Init(mSink, mChunkSize); + if (NS_FAILED(rv)) { + return rv; + } + + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + mSink = sink; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver* observer, nsISupports* ctx) { + LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%p]\n", this, + observer)); + + NS_ASSERTION(mSource && mSink, "not initialized"); + nsresult rv; + + if (observer) { + // build proxy for observer events + rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx); + if (NS_FAILED(rv)) return rv; + } + + // from this point forward, AsyncCopy is going to return NS_OK. any errors + // will be reported via OnStopRequest. + { + MutexAutoLock lock(mLock); + mIsPending = true; + } + + if (mObserver) { + rv = mObserver->OnStartRequest(AsRequest()); + if (NS_FAILED(rv)) Cancel(rv); + } + + if (!mShouldSniffBuffering) { + // No buffer sniffing required, let's proceed + AsyncCopyInternal(); + return NS_OK; + } + + if (NS_IsMainThread()) { + // Don't perform buffer sniffing on the main thread + nsCOMPtr event = new AsyncApplyBufferingPolicyEvent(this); + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + Cancel(rv); + } + return NS_OK; + } + + // We're not going to block the main thread, so let's sniff here + rv = ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + Cancel(rv); + } + AsyncCopyInternal(); + return NS_OK; +} + +// Launch async copy. +// All errors are reported through the observer. +void nsAsyncStreamCopier::AsyncCopyInternal() { + MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS || + mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS); + + nsresult rv; + // We want to receive progress notifications; release happens in + // OnAsyncCopyComplete. + RefPtr self = this; + { + MutexAutoLock lock(mLock); + rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize, + OnAsyncCopyComplete, this, mCloseSource, mCloseSink, + getter_AddRefs(mCopierCtx)); + } + if (NS_FAILED(rv)) { + Cancel(rv); + return; // release self + } + + Unused << self.forget(); // Will be released in OnAsyncCopyComplete +} diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h new file mode 100644 index 0000000000..120218c3c7 --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.h @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncStreamCopier_h__ +#define nsAsyncStreamCopier_h__ + +#include "nsIAsyncStreamCopier.h" +#include "nsIAsyncStreamCopier2.h" +#include "mozilla/Mutex.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" + +class nsIRequestObserver; + +//----------------------------------------------------------------------------- + +class nsAsyncStreamCopier final : public nsIAsyncStreamCopier, + nsIAsyncStreamCopier2 { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIASYNCSTREAMCOPIER + + // nsIAsyncStreamCopier2 + // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2 + // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier + NS_IMETHOD Init(nsIInputStream* aSource, nsIOutputStream* aSink, + nsIEventTarget* aTarget, uint32_t aChunkSize, + bool aCloseSource, bool aCloseSink) override; + + nsAsyncStreamCopier(); + + //------------------------------------------------------------------------- + // these methods may be called on any thread + + bool IsComplete(nsresult* status = nullptr); + void Complete(nsresult status); + + private: + virtual ~nsAsyncStreamCopier(); + + nsresult InitInternal(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, uint32_t chunkSize, + bool closeSource, bool closeSink); + + static void OnAsyncCopyComplete(void*, nsresult); + + void AsyncCopyInternal(); + nsresult ApplyBufferingPolicy(); + nsIRequest* AsRequest(); + + nsCOMPtr mSource; + nsCOMPtr mSink; + + nsCOMPtr mObserver; + + nsCOMPtr mTarget; + + nsCOMPtr mCopierCtx MOZ_GUARDED_BY(mLock); + + mozilla::Mutex mLock{"nsAsyncStreamCopier.mLock"}; + + nsAsyncCopyMode mMode{NS_ASYNCCOPY_VIA_READSEGMENTS}; + uint32_t mChunkSize; // only modified in Init + nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK}; + bool mIsPending MOZ_GUARDED_BY(mLock){false}; + bool mCloseSource MOZ_GUARDED_BY(mLock){false}; + bool mCloseSink MOZ_GUARDED_BY(mLock){false}; + bool mShouldSniffBuffering{false}; // only modified in Init + + friend class ProceedWithAsyncCopy; + friend class AsyncApplyBufferingPolicyEvent; +}; + +#endif // !nsAsyncStreamCopier_h__ diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp new file mode 100644 index 0000000000..11cf7626ce --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.cpp @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAuthInformationHolder.h" + +NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation) + +NS_IMETHODIMP +nsAuthInformationHolder::GetFlags(uint32_t* aFlags) { + *aFlags = mFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetRealm(nsAString& aRealm) { + aRealm = mRealm; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme) { + aScheme = mAuthType; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetUsername(nsAString& aUserName) { + aUserName = mUser; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetUsername(const nsAString& aUserName) { + if (!(mFlags & ONLY_PASSWORD)) mUser = aUserName; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetPassword(nsAString& aPassword) { + aPassword = mPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetPassword(const nsAString& aPassword) { + mPassword = aPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetDomain(nsAString& aDomain) { + aDomain = mDomain; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetDomain(const nsAString& aDomain) { + if (mFlags & NEED_DOMAIN) mDomain = aDomain; + return NS_OK; +} diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h new file mode 100644 index 0000000000..48054d9413 --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSAUTHINFORMATIONHOLDER_H_ +#define NSAUTHINFORMATIONHOLDER_H_ + +#include "nsIAuthInformation.h" +#include "nsString.h" + +class nsAuthInformationHolder : public nsIAuthInformation { + protected: + virtual ~nsAuthInformationHolder() = default; + + public: + // aAuthType must be ASCII + nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm, + const nsACString& aAuthType) + : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIAUTHINFORMATION + + const nsString& User() const { return mUser; } + const nsString& Password() const { return mPassword; } + const nsString& Domain() const { return mDomain; } + + /** + * This method can be used to initialize the username when the + * ONLY_PASSWORD flag is set. + */ + void SetUserInternal(const nsString& aUsername) { mUser = aUsername; } + + private: + nsString mUser; + nsString mPassword; + nsString mDomain; + + uint32_t mFlags; + nsString mRealm; + nsCString mAuthType; +}; + +#endif diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp new file mode 100644 index 0000000000..621dd2c4ec --- /dev/null +++ b/netwerk/base/nsBase64Encoder.cpp @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBase64Encoder.h" + +#include "mozilla/Base64.h" + +NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream) + +NS_IMETHODIMP +nsBase64Encoder::Close() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::Flush() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::StreamStatus() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) { + mData.Append(aBuf, aCount); + *_retval = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount, + uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} + +nsresult nsBase64Encoder::Finish(nsACString& result) { + nsresult rv = mozilla::Base64Encode(mData, result); + if (NS_FAILED(rv)) { + return rv; + } + + // Free unneeded memory and allow reusing the object + mData.Truncate(); + return NS_OK; +} diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h new file mode 100644 index 0000000000..b74d3c9b5a --- /dev/null +++ b/netwerk/base/nsBase64Encoder.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSBASE64ENCODER_H_ +#define NSBASE64ENCODER_H_ + +#include "nsIOutputStream.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +/** + * A base64 encoder. Usage: Instantiate class, write to it using + * Write(), then call Finish() to get the base64-encoded data. + */ +class nsBase64Encoder final : public nsIOutputStream { + public: + nsBase64Encoder() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsresult Finish(nsACString& _result); + + private: + ~nsBase64Encoder() = default; + + /// The data written to this stream. nsCString can deal fine with + /// binary data. + nsCString mData; +}; + +#endif diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp new file mode 100644 index 0000000000..3ce7fb9d4b --- /dev/null +++ b/netwerk/base/nsBaseChannel.cpp @@ -0,0 +1,951 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseChannel.h" +#include "nsContentUtils.h" +#include "nsURLHelper.h" +#include "nsNetCID.h" +#include "nsMimeTypes.h" +#include "nsUnknownDecoder.h" +#include "nsIScriptSecurityManager.h" +#include "nsMimeTypes.h" +#include "nsICancelable.h" +#include "nsIChannelEventSink.h" +#include "nsIStreamConverterService.h" +#include "nsChannelClassifier.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsProxyRelease.h" +#include "nsXULAppAPI.h" +#include "nsContentSecurityManager.h" +#include "LoadInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsRedirectHistoryEntry.h" +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/BasePrincipal.h" + +using namespace mozilla; + +// This class is used to suspend a request across a function scope. +class ScopedRequestSuspender { + public: + explicit ScopedRequestSuspender(nsIRequest* request) : mRequest(request) { + if (mRequest && NS_FAILED(mRequest->Suspend())) { + NS_WARNING("Couldn't suspend pump"); + mRequest = nullptr; + } + } + ~ScopedRequestSuspender() { + if (mRequest) mRequest->Resume(); + } + + private: + nsIRequest* mRequest; +}; + +// Used to suspend data events from mRequest within a function scope. This is +// usually needed when a function makes callbacks that could process events. +#define SUSPEND_PUMP_FOR_SCOPE() \ + ScopedRequestSuspender pump_suspender__(mRequest) + +//----------------------------------------------------------------------------- +// nsBaseChannel + +nsBaseChannel::nsBaseChannel() : NeckoTargetHolder(nullptr) { + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsBaseChannel::~nsBaseChannel() { + NS_ReleaseOnMainThread("nsBaseChannel::mLoadInfo", mLoadInfo.forget()); +} + +nsresult nsBaseChannel::Redirect(nsIChannel* newChannel, uint32_t redirectFlags, + bool openNewChannel) { + SUSPEND_PUMP_FOR_SCOPE(); + + // Transfer properties + + newChannel->SetLoadGroup(mLoadGroup); + newChannel->SetNotificationCallbacks(mCallbacks); + newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE); + + // make a copy of the loadinfo, append to the redirectchain + // and set it on the new channel + nsSecurityFlags secFlags = + mLoadInfo->GetSecurityFlags() & ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + nsCOMPtr newLoadInfo = + static_cast(mLoadInfo.get()) + ->CloneWithNewSecFlags(secFlags); + + bool isInternalRedirect = + (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE)); + + newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect); + + // Ensure the channel's loadInfo's result principal URI so that it's + // either non-null or updated to the redirect target URI. + // We must do this because in case the loadInfo's result principal URI + // is null, it would be taken from OriginalURI of the channel. But we + // overwrite it with the whole redirect chain first URI before opening + // the target channel, hence the information would be lost. + // If the protocol handler that created the channel wants to use + // the originalURI of the channel as the principal URI, it has left + // the result principal URI on the load info null. + nsCOMPtr resultPrincipalURI; + + nsCOMPtr existingLoadInfo = newChannel->LoadInfo(); + if (existingLoadInfo) { + existingLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + } + if (!resultPrincipalURI) { + newChannel->GetOriginalURI(getter_AddRefs(resultPrincipalURI)); + } + + newLoadInfo->SetResultPrincipalURI(resultPrincipalURI); + + newChannel->SetLoadInfo(newLoadInfo); + + // Preserve the privacy bit if it has been overridden + if (mPrivateBrowsingOverriden) { + nsCOMPtr newPBChannel = + do_QueryInterface(newChannel); + if (newPBChannel) { + newPBChannel->SetPrivate(mPrivateBrowsing); + } + } + + if (nsCOMPtr bag = ::do_QueryInterface(newChannel)) { + nsHashPropertyBag::CopyFrom(bag, static_cast(this)); + } + + // Notify consumer, giving chance to cancel redirect. + + auto redirectCallbackHelper = MakeRefPtr(); + + bool checkRedirectSynchronously = !openNewChannel; + nsCOMPtr target = GetNeckoTarget(); + + mRedirectChannel = newChannel; + mRedirectFlags = redirectFlags; + mOpenRedirectChannel = openNewChannel; + nsresult rv = redirectCallbackHelper->Init( + this, newChannel, redirectFlags, target, checkRedirectSynchronously); + if (NS_FAILED(rv)) return rv; + + if (checkRedirectSynchronously && NS_FAILED(mStatus)) return mStatus; + + return NS_OK; +} + +nsresult nsBaseChannel::ContinueRedirect() { + // Make sure to do this _after_ making all the OnChannelRedirect calls + mRedirectChannel->SetOriginalURI(OriginalURI()); + + // If we fail to open the new channel, then we want to leave this channel + // unaffected, so we defer tearing down our channel until we have succeeded + // with the redirect. + + if (mOpenRedirectChannel) { + nsresult rv = NS_OK; + rv = mRedirectChannel->AsyncOpen(mListener); + NS_ENSURE_SUCCESS(rv, rv); + } + + mRedirectChannel = nullptr; + + // close down this channel + Cancel(NS_BINDING_REDIRECTED); + ChannelDone(); + + return NS_OK; +} + +bool nsBaseChannel::HasContentTypeHint() const { + NS_ASSERTION(!Pending(), "HasContentTypeHint called too late"); + return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsresult nsBaseChannel::BeginPumpingData() { + nsresult rv; + + rv = BeginAsyncRead(this, getter_AddRefs(mRequest), + getter_AddRefs(mCancelableAsyncRequest)); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(mRequest || mCancelableAsyncRequest, + "should have got a request or cancelable"); + mPumpingData = true; + return NS_OK; + } + if (rv != NS_ERROR_NOT_IMPLEMENTED) { + return rv; + } + + nsCOMPtr stream; + nsCOMPtr channel; + rv = OpenContentStream(true, getter_AddRefs(stream), getter_AddRefs(channel)); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?"); + + if (channel) { + nsCOMPtr runnable = new RedirectRunnable(this, channel); + rv = Dispatch(runnable.forget()); + if (NS_SUCCEEDED(rv)) mWaitingOnAsyncRedirect = true; + return rv; + } + + // By assigning mPump, we flag this channel as pending (see Pending). It's + // important that the pending flag is set when we call into the stream (the + // call to AsyncRead results in the stream's AsyncWait method being called) + // and especially when we call into the loadgroup. Our caller takes care to + // release mPump if we return an error. + + nsCOMPtr target = GetNeckoTarget(); + rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true, + target); + if (NS_FAILED(rv)) { + return rv; + } + + mPumpingData = true; + mRequest = mPump; + rv = mPump->AsyncRead(this); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr promise; + rv = ListenerBlockingPromise(getter_AddRefs(promise)); + if (NS_FAILED(rv)) { + return rv; + } + + if (promise) { + mPump->Suspend(); + + RefPtr self(this); + + promise->Then( + target, __func__, + [self, this](nsresult rv) { + MOZ_ASSERT(mPump); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + mPump->Resume(); + }, + [self, this](nsresult rv) { + MOZ_ASSERT(mPump); + MOZ_ASSERT(NS_FAILED(rv)); + Cancel(rv); + mPump->Resume(); + }); + } + + return NS_OK; +} + +void nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) { + NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here"); + + nsresult rv = mStatus; + if (NS_SUCCEEDED(mStatus)) { + rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true); + if (NS_SUCCEEDED(rv)) { + // OnRedirectVerifyCallback will be called asynchronously + return; + } + } + + ContinueHandleAsyncRedirect(rv); +} + +void nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result) { + mWaitingOnAsyncRedirect = false; + + if (NS_FAILED(result)) Cancel(result); + + if (NS_FAILED(result) && mListener) { + // Notify our consumer ourselves + mListener->OnStartRequest(this); + mListener->OnStopRequest(this, mStatus); + ChannelDone(); + } + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); +} + +void nsBaseChannel::ClassifyURI() { + // For channels created in the child process, delegate to the parent to + // classify URIs. + if (!XRE_IsParentProcess()) { + return; + } + + if (NS_ShouldClassifyChannel(this)) { + auto classifier = MakeRefPtr(this); + classifier->Start(); + } +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsISupports + +NS_IMPL_ADDREF(nsBaseChannel) +NS_IMPL_RELEASE(nsBaseChannel) + +NS_INTERFACE_MAP_BEGIN(nsBaseChannel) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIChannel) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) + NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel) +NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag) + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequest + +NS_IMETHODIMP +nsBaseChannel::GetName(nsACString& result) { + if (!mURI) { + result.Truncate(); + return NS_OK; + } + return mURI->GetSpec(result); +} + +NS_IMETHODIMP +nsBaseChannel::IsPending(bool* result) { + *result = Pending(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetStatus(nsresult* status) { + if (mRequest && NS_SUCCEEDED(mStatus)) { + mRequest->GetStatus(status); + } else { + *status = mStatus; + } + return NS_OK; +} + +NS_IMETHODIMP nsBaseChannel::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsBaseChannel::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsBaseChannel::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsBaseChannel::Cancel(nsresult status) { + // Ignore redundant cancelation + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + mStatus = status; + + if (mCancelableAsyncRequest) { + mCancelableAsyncRequest->Cancel(status); + } + + if (mRequest) { + mRequest->Cancel(status); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Suspend() { + NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED); + return mRequest->Suspend(); +} + +NS_IMETHODIMP +nsBaseChannel::Resume() { + NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED); + return mRequest->Resume(); +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { + nsCOMPtr loadGroup(mLoadGroup); + loadGroup.forget(aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { + if (!CanSetLoadGroup(aLoadGroup)) { + return NS_ERROR_FAILURE; + } + + mLoadGroup = aLoadGroup; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIChannel + +NS_IMETHODIMP +nsBaseChannel::GetOriginalURI(nsIURI** aURI) { + RefPtr uri = OriginalURI(); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOriginalURI(nsIURI* aURI) { + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetURI(nsIURI** aURI) { + nsCOMPtr uri(mURI); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetOwner(nsISupports** aOwner) { + nsCOMPtr owner(mOwner); + owner.forget(aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOwner(nsISupports* aOwner) { + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { + MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null"); + mLoadInfo = aLoadInfo; + + // Need to update |mNeckoTarget| when load info has changed. + SetupNeckoTarget(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { + nsCOMPtr loadInfo(mLoadInfo); + loadInfo.forget(aLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetIsDocument(bool* aIsDocument) { + return NS_GetIsDocumentChannel(this, aIsDocument); +} + +NS_IMETHODIMP +nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) { + nsCOMPtr callbacks(mCallbacks); + callbacks.forget(aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { + if (!CanSetCallbacks(aCallbacks)) { + return NS_ERROR_FAILURE; + } + + mCallbacks = aCallbacks; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { + *aSecurityInfo = do_AddRef(mSecurityInfo).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentType(nsACString& aContentType) { + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentType(const nsACString& aContentType) { + // mContentCharset is unchanged if not parsed + bool dummy; + net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentCharset(nsACString& aContentCharset) { + aContentCharset = mContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentCharset(const nsACString& aContentCharset) { + mContentCharset = aContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) { + // preserve old behavior, fail unless explicitly set. + if (mContentDispositionHint == UINT32_MAX) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aContentDisposition = mContentDispositionHint; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition) { + mContentDispositionHint = aContentDisposition; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + if (!mContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; + } + + aContentDispositionFilename = *mContentDispositionFilename; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + mContentDispositionFilename = + MakeUnique(aContentDispositionFilename); + + // For safety reasons ensure the filename doesn't contain null characters and + // replace them with underscores. We may later pass the extension to system + // MIME APIs that expect null terminated strings. + mContentDispositionFilename->ReplaceChar(char16_t(0), '_'); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentLength(int64_t* aContentLength) { + *aContentLength = mContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentLength(int64_t aContentLength) { + mContentLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Open(nsIInputStream** aStream) { + nsCOMPtr listener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS); + + nsCOMPtr chan; + rv = OpenContentStream(false, aStream, getter_AddRefs(chan)); + NS_ASSERTION(!chan || !*aStream, "Got both a channel and a stream?"); + if (NS_SUCCEEDED(rv) && chan) { + rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false); + if (NS_FAILED(rv)) return rv; + rv = chan->Open(aStream); + } else if (rv == NS_ERROR_NOT_IMPLEMENTED) { + return NS_ImplementChannelOpen(this, aStream); + } + + if (NS_SUCCEEDED(rv)) { + mWasOpened = true; + ClassifyURI(); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen(nsIStreamListener* aListener) { + nsCOMPtr listener = aListener; + + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + MOZ_ASSERT( + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && + mLoadInfo->GetLoadingPrincipal() && + mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), + "security flags in loadInfo but doContentSecurityCheck() not called"); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + NS_ENSURE_ARG(listener); + + SetupNeckoTarget(); + + // Skip checking for chrome:// sub-resources. + nsAutoCString scheme; + mURI->GetScheme(scheme); + if (!scheme.EqualsLiteral("file")) { + NS_CompareLoadInfoAndLoadContext(this); + } + + // Ensure that this is an allowed port before proceeding. + rv = NS_CheckPortSafety(mURI); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this); + + // Store the listener and context early so that OpenContentStream and the + // stream's AsyncWait method (called by AsyncRead) can have access to them + // via the StreamListener methods. However, since + // this typically introduces a reference cycle between this and the listener, + // we need to be sure to break the reference if this method does not succeed. + mListener = listener; + + // This method assigns mPump as a side-effect. We need to clear mPump if + // this method fails. + rv = BeginPumpingData(); + if (NS_FAILED(rv)) { + mPump = nullptr; + mRequest = nullptr; + mPumpingData = false; + ChannelDone(); + mCallbacks = nullptr; + return rv; + } + + // At this point, we are going to return success no matter what. + + mWasOpened = true; + + SUSPEND_PUMP_FOR_SCOPE(); + + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + ClassifyURI(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsITransportEventSink + +NS_IMETHODIMP +nsBaseChannel::OnTransportStatus(nsITransport* transport, nsresult status, + int64_t progress, int64_t progressMax) { + // In some cases, we may wish to suppress transport-layer status events. + + if (!mPumpingData || NS_FAILED(mStatus)) { + return NS_OK; + } + + SUSPEND_PUMP_FOR_SCOPE(); + + // Lazily fetch mProgressSink + if (!mProgressSink) { + if (mQueriedProgressSink) { + return NS_OK; + } + GetCallback(mProgressSink); + mQueriedProgressSink = true; + if (!mProgressSink) { + return NS_OK; + } + } + + if (!HasLoadFlag(LOAD_BACKGROUND)) { + nsAutoString statusArg; + if (GetStatusArg(status, statusArg)) { + mProgressSink->OnStatus(this, status, statusArg.get()); + } + } + + if (progress) { + mProgressSink->OnProgress(this, progress, progressMax); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIInterfaceRequestor + +NS_IMETHODIMP +nsBaseChannel::GetInterface(const nsIID& iid, void** result) { + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result); + return *result ? NS_OK : NS_ERROR_NO_INTERFACE; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequestObserver + +static void CallTypeSniffers(void* aClosure, const uint8_t* aData, + uint32_t aCount) { + nsIChannel* chan = static_cast(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(aClosure); + + RefPtr sniffer = new nsUnknownDecoder(); + + nsAutoCString detected; + nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected); + if (NS_SUCCEEDED(rv)) chan->SetContentType(detected); +} + +NS_IMETHODIMP +nsBaseChannel::OnStartRequest(nsIRequest* request) { + MOZ_ASSERT_IF(mRequest, request == mRequest); + MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest); + + nsAutoCString scheme; + mURI->GetScheme(scheme); + + if (mPump && !scheme.EqualsLiteral("ftp")) { + // 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(this)); + } + + // Now, the general type sniffers. Skip this if we have none. + if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { + mPump->PeekStream(CallTypeSniffers, static_cast(this)); + } + } + + SUSPEND_PUMP_FOR_SCOPE(); + + if (mListener) { // null in case of redirect + return mListener->OnStartRequest(this); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::OnStopRequest(nsIRequest* request, nsresult status) { + // If both mStatus and status are failure codes, we keep mStatus as-is since + // that is consistent with our GetStatus and Cancel methods. + if (NS_SUCCEEDED(mStatus)) mStatus = status; + + // Cause Pending to return false. + mPump = nullptr; + mRequest = nullptr; + mCancelableAsyncRequest = nullptr; + mPumpingData = false; + + if (mListener) { // null in case of redirect + mListener->OnStopRequest(this, mStatus); + } + ChannelDone(); + + // No need to suspend pump in this scope since we will not be receiving + // any more events from it. + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIStreamListener + +NS_IMETHODIMP +nsBaseChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* stream, + uint64_t offset, uint32_t count) { + SUSPEND_PUMP_FOR_SCOPE(); + + nsresult rv = mListener->OnDataAvailable(this, stream, offset, count); + if (mSynthProgressEvents && NS_SUCCEEDED(rv)) { + int64_t prog = offset + count; + if (NS_IsMainThread()) { + OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength); + } else { + class OnTransportStatusAsyncEvent : public Runnable { + RefPtr mChannel; + int64_t mProgress; + int64_t mContentLength; + + public: + OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, int64_t aProgress, + int64_t aContentLength) + : Runnable("OnTransportStatusAsyncEvent"), + mChannel(aChannel), + mProgress(aProgress), + mContentLength(aContentLength) {} + + NS_IMETHOD Run() override { + return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING, + mProgress, mContentLength); + } + }; + + nsCOMPtr runnable = + new OnTransportStatusAsyncEvent(this, prog, mContentLength); + Dispatch(runnable.forget()); + } + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::OnRedirectVerifyCallback(nsresult result) { + if (NS_SUCCEEDED(result)) result = ContinueRedirect(); + + if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) { + if (NS_SUCCEEDED(mStatus)) mStatus = result; + return NS_OK; + } + + if (mWaitingOnAsyncRedirect) ContinueHandleAsyncRedirect(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::RetargetDeliveryTo(nsISerialEventTarget* aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr req; + if (mAllowThreadRetargeting) { + req = do_QueryInterface(mRequest); + } + + NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED); + + return req->RetargetDeliveryTo(aEventTarget); +} + +NS_IMETHODIMP +nsBaseChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr req; + req = do_QueryInterface(mRequest); + + NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED); + return req->GetDeliveryTarget(aEventTarget); +} + +NS_IMETHODIMP +nsBaseChannel::CheckListenerChain() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr listener = + do_QueryInterface(mListener); + if (!listener) { + return NS_ERROR_NO_INTERFACE; + } + + return listener->CheckListenerChain(); +} + +NS_IMETHODIMP nsBaseChannel::GetCanceled(bool* aCanceled) { + *aCanceled = mCanceled; + return NS_OK; +} + +void nsBaseChannel::SetupNeckoTarget() { + mNeckoTarget = + nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other); +} diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h new file mode 100644 index 0000000000..cacb74231b --- /dev/null +++ b/netwerk/base/nsBaseChannel.h @@ -0,0 +1,310 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseChannel_h__ +#define nsBaseChannel_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/net/NeckoTargetHolder.h" +#include "mozilla/net/PrivateBrowsingChannel.h" +#include "nsHashPropertyBag.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIProgressEventSink.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsITransport.h" +#include "nsITransportSecurityInfo.h" +#include "nsIURI.h" +#include "nsInputStreamPump.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" + +class nsIInputStream; +class nsICancelable; + +//----------------------------------------------------------------------------- +// nsBaseChannel is designed to be subclassed. The subclass is responsible for +// implementing the OpenContentStream method, which will be called by the +// nsIChannel::AsyncOpen and nsIChannel::Open implementations. +// +// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way +// for subclasses to query both the nsIChannel::notificationCallbacks and +// nsILoadGroup::notificationCallbacks for supported interfaces. +// +// nsBaseChannel implements nsITransportEventSink to support progress & status +// notifications generated by the transport layer. + +class nsBaseChannel + : public nsHashPropertyBag, + public nsIChannel, + public nsIThreadRetargetableRequest, + public nsIInterfaceRequestor, + public nsITransportEventSink, + public nsIAsyncVerifyRedirectCallback, + public mozilla::net::PrivateBrowsingChannel, + public mozilla::net::NeckoTargetHolder, + 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(); + + protected: + // ----------------------------------------------- + // Methods to be implemented by the derived class: + + virtual ~nsBaseChannel(); + + using BlockingPromise = mozilla::MozPromise; + + private: + // Implemented by subclass to supply data stream. The parameter, async, is + // true when called from nsIChannel::AsyncOpen and false otherwise. When + // async is true, the resulting stream will be used with a nsIInputStreamPump + // instance. This means that if it is a non-blocking stream that supports + // nsIAsyncInputStream that it will be read entirely on the main application + // thread, and its AsyncWait method will be called whenever ReadSegments + // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking, + // then it will be read on one of the background I/O threads, and it does not + // need to implement ReadSegments. If async is false, this method may return + // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in + // terms of AsyncOpen (see NS_ImplementChannelOpen). + // A callee is allowed to return an nsIChannel instead of an nsIInputStream. + // That case will be treated as a redirect to the new channel. By default + // *channel will be set to null by the caller, so callees who don't want to + // return one an just not touch it. + virtual nsresult OpenContentStream(bool async, nsIInputStream** stream, + nsIChannel** channel) = 0; + + // Implemented by subclass to begin pumping data for an async channel, in + // lieu of returning a stream. If implemented, OpenContentStream will never + // be called for async channels. If not implemented, AsyncOpen will fall + // back to OpenContentStream. + // + // On success, the callee must begin pumping data to the stream listener, + // and at some point call OnStartRequest followed by OnStopRequest. + // + // Additionally, when a successful nsresult is returned, then the subclass + // should be setting through its two out params either: + // - a request object, which may be used to suspend, resume, and cancel + // the underlying request. + // - or a cancelable object (e.g. when a request can't be returned right away + // due to some async work needed to retrieve it). which may be used to + // cancel the underlying request (e.g. because the channel has been + // canceled) + // + // Not returning a request or cancelable leads to potentially leaking the + // an underling stream pump (which would keep to be pumping data even after + // the channel has been canceled and nothing is going to handle the data + // available, e.g. see Bug 1706594). + virtual nsresult BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // This method may return a promise that will keep the input stream pump + // suspended until the promise is resolved or rejected. On resolution the + // pump is resumed. On rejection the channel is canceled with the resulting + // error and then the pump is also resumed to propagate the error to the + // channel listener. Use it to do any asynchronous/background tasks you need + // to finish prior calling OnStartRequest of the listener. This method is + // called right after OpenContentStream() with async == true, after the input + // stream pump has already been called asyncRead(). + virtual nsresult ListenerBlockingPromise(BlockingPromise** aPromise) { + NS_ENSURE_ARG(aPromise); + *aPromise = nullptr; + return NS_OK; + } + + // The basechannel calls this method from its OnTransportStatus method to + // determine whether to call nsIProgressEventSink::OnStatus in addition to + // nsIProgressEventSink::OnProgress. This method may be overriden by the + // subclass to enable nsIProgressEventSink::OnStatus events. If this method + // returns true, then the statusArg out param specifies the "statusArg" value + // to pass to the OnStatus method. By default, OnStatus messages are + // suppressed. The status parameter passed to this method is the status value + // from the OnTransportStatus method. + virtual bool GetStatusArg(nsresult status, nsString& statusArg) { + return false; + } + + // Called when the callbacks available to this channel may have changed. + virtual void OnCallbacksChanged() {} + + // Called when our channel is done, to allow subclasses to drop resources. + virtual void OnChannelDone() {} + + public: + // ---------------------------------------------- + // Methods provided for use by the derived class: + + // Redirect to another channel. This method takes care of notifying + // observers of this redirect as well as of opening the new channel, if asked + // to do so. It also cancels |this| with the status code + // NS_BINDING_REDIRECTED. A failure return from this method means that the + // redirect could not be performed (no channel was opened; this channel + // wasn't canceled.) The redirectFlags parameter consists of the flag values + // defined on nsIChannelEventSink. + nsresult Redirect(nsIChannel* newChannel, uint32_t redirectFlags, + bool openNewChannel); + + // Tests whether a type hint was set. Subclasses can use this to decide + // whether to call SetContentType. + // NOTE: This is only reliable if the subclass didn't itself call + // SetContentType, and should also not be called after OpenContentStream. + bool HasContentTypeHint() const; + + // The URI member should be initialized before the channel is used, and then + // it should never be changed again until the channel is destroyed. + nsIURI* URI() { return mURI; } + void SetURI(nsIURI* uri) { + NS_ASSERTION(uri, "must specify a non-null URI"); + NS_ASSERTION(!mURI, "must not modify URI"); + NS_ASSERTION(!mOriginalURI, "how did that get set so early?"); + mURI = uri; + mOriginalURI = uri; + } + nsIURI* OriginalURI() { return mOriginalURI; } + + // The security info is a property of the transport-layer, which should be + // assigned by the subclass. + nsITransportSecurityInfo* SecurityInfo() { return mSecurityInfo; } + void SetSecurityInfo(nsITransportSecurityInfo* info) { mSecurityInfo = info; } + + // Test the load flags + bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; } + + // This is a short-cut to calling nsIRequest::IsPending() + virtual bool Pending() const { + return mPumpingData || mWaitingOnAsyncRedirect; + } + + // Helper function for querying the channel's notification callbacks. + template + void GetCallback(nsCOMPtr& result) { + GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result)); + } + + // If a subclass does not want to feed transport-layer progress events to the + // base channel via nsITransportEventSink, then it may set this flag to cause + // the base channel to synthesize progress events when it receives data from + // the content stream. By default, progress events are not synthesized. + void EnableSynthesizedProgressEvents(bool enable) { + mSynthProgressEvents = enable; + } + + // Some subclasses may wish to manually insert a stream listener between this + // and the channel's listener. The following methods make that possible. + void SetStreamListener(nsIStreamListener* listener) { mListener = listener; } + nsIStreamListener* StreamListener() { return mListener; } + + protected: + void DisallowThreadRetargeting() { mAllowThreadRetargeting = false; } + + virtual void SetupNeckoTarget(); + + private: + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + // Called to setup mPump and call AsyncRead on it. + nsresult BeginPumpingData(); + + // Called when the callbacks available to this channel may have changed. + void CallbacksChanged() { + mProgressSink = nullptr; + mQueriedProgressSink = false; + OnCallbacksChanged(); + } + + // Called when our channel is done. This should drop no-longer-needed + // pointers. + void ChannelDone() { + mListener = nullptr; + OnChannelDone(); + } + + // Handle an async redirect callback. This will only be called if we + // returned success from AsyncOpen while posting a redirect runnable. + void HandleAsyncRedirect(nsIChannel* newChannel); + void ContinueHandleAsyncRedirect(nsresult result); + nsresult ContinueRedirect(); + + // start URI classifier if requested + void ClassifyURI(); + + class RedirectRunnable : public mozilla::Runnable { + public: + RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel) + : mozilla::Runnable("nsBaseChannel::RedirectRunnable"), + mChannel(chan), + mNewChannel(newChannel) { + MOZ_ASSERT(newChannel, "Must have channel to redirect to"); + } + + NS_IMETHOD Run() override { + mChannel->HandleAsyncRedirect(mNewChannel); + return NS_OK; + } + + private: + RefPtr mChannel; + nsCOMPtr mNewChannel; + }; + friend class RedirectRunnable; + + RefPtr mPump; + RefPtr mRequest; + nsCOMPtr mCancelableAsyncRequest; + bool mPumpingData{false}; + nsCOMPtr mProgressSink; + nsCOMPtr mOriginalURI; + nsCOMPtr mOwner; + nsCOMPtr mSecurityInfo; + nsCOMPtr mRedirectChannel; + uint32_t mLoadFlags{LOAD_NORMAL}; + bool mQueriedProgressSink{true}; + bool mSynthProgressEvents{false}; + bool mAllowThreadRetargeting{true}; + bool mWaitingOnAsyncRedirect{false}; + bool mOpenRedirectChannel{false}; + uint32_t mRedirectFlags{0}; + + protected: + nsCString mContentType; + nsCString mContentCharset; + nsCOMPtr mURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mLoadInfo; + nsCOMPtr mCallbacks; + nsCOMPtr mListener; + nsresult mStatus{NS_OK}; + uint32_t mContentDispositionHint{UINT32_MAX}; + mozilla::UniquePtr mContentDispositionFilename; + int64_t mContentLength{-1}; + bool mWasOpened{false}; + bool mCanceled{false}; + + friend class mozilla::net::PrivateBrowsingChannel; +}; + +#endif // !nsBaseChannel_h__ diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp new file mode 100644 index 0000000000..d59145f463 --- /dev/null +++ b/netwerk/base/nsBaseContentStream.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseContentStream.h" +#include "nsStreamUtils.h" + +//----------------------------------------------------------------------------- + +void nsBaseContentStream::DispatchCallback(bool async) { + if (!mCallback) return; + + // It's important to clear mCallback and mCallbackTarget up-front because the + // OnInputStreamReady implementation may call our AsyncWait method. + + nsCOMPtr callback; + if (async) { + callback = NS_NewInputStreamReadyEvent( + "nsBaseContentStream::DispatchCallback", mCallback, mCallbackTarget); + mCallback = nullptr; + } else { + callback.swap(mCallback); + } + mCallbackTarget = nullptr; + + callback->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsISupports + +NS_IMPL_ADDREF(nsBaseContentStream) +NS_IMPL_RELEASE(nsBaseContentStream) + +// We only support nsIAsyncInputStream when we are in non-blocking mode. +NS_INTERFACE_MAP_BEGIN(nsBaseContentStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIInputStream + +NS_IMETHODIMP +nsBaseContentStream::Close() { + return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsBaseContentStream::Available(uint64_t* result) { + *result = 0; + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::StreamStatus() { return mStatus; } + +NS_IMETHODIMP +nsBaseContentStream::Read(char* buf, uint32_t count, uint32_t* result) { + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure, + uint32_t count, uint32_t* result) { + *result = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) return NS_OK; + + // No data yet + if (!IsClosed() && IsNonBlocking()) return NS_BASE_STREAM_WOULD_BLOCK; + + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::IsNonBlocking(bool* result) { + *result = mNonBlocking; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIAsyncInputStream + +NS_IMETHODIMP +nsBaseContentStream::CloseWithStatus(nsresult status) { + if (IsClosed()) return NS_OK; + + NS_ENSURE_ARG(NS_FAILED(status)); + mStatus = status; + + DispatchCallback(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseContentStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags, + uint32_t requestedCount, + nsIEventTarget* target) { + // Our _only_ consumer is nsInputStreamPump, so we simplify things here by + // making assumptions about how we will be called. + NS_ASSERTION(target, "unexpected parameter"); + NS_ASSERTION(flags == 0, "unexpected parameter"); + NS_ASSERTION(requestedCount == 0, "unexpected parameter"); + +#ifdef DEBUG + bool correctThread; + target->IsOnCurrentThread(&correctThread); + NS_ASSERTION(correctThread, "event target must be on the current thread"); +#endif + + mCallback = callback; + mCallbackTarget = target; + + if (!mCallback) return NS_OK; + + // If we're already closed, then dispatch this callback immediately. + if (IsClosed()) { + DispatchCallback(); + return NS_OK; + } + + OnCallbackPending(); + return NS_OK; +} diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h new file mode 100644 index 0000000000..13a3766857 --- /dev/null +++ b/netwerk/base/nsBaseContentStream.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseContentStream_h__ +#define nsBaseContentStream_h__ + +#include "nsIAsyncInputStream.h" +#include "nsIEventTarget.h" +#include "nsCOMPtr.h" + +//----------------------------------------------------------------------------- +// nsBaseContentStream is designed to be subclassed with the intention of being +// used to satisfy the nsBaseChannel::OpenContentStream method. +// +// The subclass typically overrides the default Available, ReadSegments and +// CloseWithStatus methods. By default, Read is implemented in terms of +// ReadSegments, and Close is implemented in terms of CloseWithStatus. If +// CloseWithStatus is overriden, then the subclass will usually want to call +// the base class' CloseWithStatus method before returning. +// +// If the stream is non-blocking, then readSegments may return the exception +// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is +// not at the "end-of-file" or already closed. This error code must not be +// returned from the Available implementation. When the caller receives this +// error code, he may choose to call the stream's AsyncWait method, in which +// case the base stream will have a non-null PendingCallback. When the stream +// has data or encounters an error, it should be sure to dispatch a pending +// callback if one exists (see DispatchCallback). The implementation of the +// base stream's CloseWithStatus (and Close) method will ensure that any +// pending callback is dispatched. It is the responsibility of the subclass +// to ensure that the pending callback is dispatched when it wants to have its +// ReadSegments method called again. + +class nsBaseContentStream : public nsIAsyncInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsBaseContentStream(bool nonBlocking) + : mStatus(NS_OK), mNonBlocking(nonBlocking) {} + + nsresult Status() { return mStatus; } + bool IsNonBlocking() { return mNonBlocking; } + bool IsClosed() { return NS_FAILED(mStatus); } + + // Called to test if the stream has a pending callback. + bool HasPendingCallback() { return mCallback != nullptr; } + + // The current dispatch target (may be null) for the pending callback if any. + nsIEventTarget* CallbackTarget() { return mCallbackTarget; } + + // Called to dispatch a pending callback. If there is no pending callback, + // then this function does nothing. Pass true to this function to cause the + // callback to occur asynchronously; otherwise, the callback will happen + // before this function returns. + void DispatchCallback(bool async = true); + + // Helper function to make code more self-documenting. + void DispatchCallbackSync() { DispatchCallback(false); } + + protected: + virtual ~nsBaseContentStream() = default; + + private: + // Called from the base stream's AsyncWait method when a pending callback + // is installed on the stream. + virtual void OnCallbackPending() {} + + private: + nsCOMPtr mCallback; + nsCOMPtr mCallbackTarget; + nsresult mStatus; + bool mNonBlocking; +}; + +#endif // nsBaseContentStream_h__ diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp new file mode 100644 index 0000000000..2606352cc4 --- /dev/null +++ b/netwerk/base/nsBufferedStreams.cpp @@ -0,0 +1,1197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBufferedStreams.h" +#include "nsStreamUtils.h" +#include "nsNetCID.h" +#include "nsIClassInfoImpl.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include + +#ifdef DEBUG_brendan +# define METERING +#endif + +#ifdef METERING +# include +# define METER(x) x +# define MAX_BIG_SEEKS 20 + +static struct { + uint32_t mSeeksWithinBuffer; + uint32_t mSeeksOutsideBuffer; + uint32_t mBufferReadUponSeek; + uint32_t mBufferUnreadUponSeek; + uint32_t mBytesReadFromBuffer; + uint32_t mBigSeekIndex; + struct { + int64_t mOldOffset; + int64_t mNewOffset; + } mBigSeek[MAX_BIG_SEEKS]; +} bufstats; +#else +# define METER(x) /* nothing */ +#endif + +using namespace mozilla::ipc; +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedStream + +nsBufferedStream::~nsBufferedStream() { Close(); } + +NS_IMPL_ADDREF(nsBufferedStream) +NS_IMPL_RELEASE(nsBufferedStream) + +NS_INTERFACE_MAP_BEGIN(nsBufferedStream) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsITellableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable) +NS_INTERFACE_MAP_END + +nsresult nsBufferedStream::Init(nsISupports* aStream, uint32_t bufferSize) { + NS_ASSERTION(aStream, "need to supply a stream"); + NS_ASSERTION(mStream == nullptr, "already inited"); + mStream = aStream; // we keep a reference until nsBufferedStream::Close + mBufferSize = bufferSize; + mBufferStartOffset = 0; + mCursor = 0; + nsCOMPtr seekable = do_QueryInterface(mStream); + mSeekable = seekable; + RecursiveMutexAutoLock lock(mBufferMutex); + mBuffer = new (mozilla::fallible) char[bufferSize]; + if (mBuffer == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void nsBufferedStream::Close() { + // Drop the reference from nsBufferedStream::Init() + mStream = nullptr; + RecursiveMutexAutoLock lock(mBufferMutex); + if (mBuffer) { + delete[] mBuffer; + mBuffer = nullptr; + mBufferSize = 0; + mBufferStartOffset = 0; + mCursor = 0; + mFillPoint = 0; + } +#ifdef METERING + { + static FILE* tfp; + if (!tfp) { + tfp = fopen("/tmp/bufstats", "w"); + if (tfp) { + setvbuf(tfp, nullptr, _IOLBF, 0); + } + } + if (tfp) { + fprintf(tfp, "seeks within buffer: %u\n", bufstats.mSeeksWithinBuffer); + fprintf(tfp, "seeks outside buffer: %u\n", + bufstats.mSeeksOutsideBuffer); + fprintf(tfp, "buffer read on seek: %u\n", + bufstats.mBufferReadUponSeek); + fprintf(tfp, "buffer unread on seek: %u\n", + bufstats.mBufferUnreadUponSeek); + fprintf(tfp, "bytes read from buffer: %u\n", + bufstats.mBytesReadFromBuffer); + for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) { + fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n", i, + bufstats.mBigSeek[i].mOldOffset, + bufstats.mBigSeek[i].mNewOffset); + } + } + } +#endif +} + +NS_IMETHODIMP +nsBufferedStream::Seek(int32_t whence, int64_t offset) { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + // If the underlying stream isn't a random access store, then fail early. + // We could possibly succeed for the case where the seek position denotes + // something that happens to be read into the buffer, but that would make + // the failure data-dependent. + nsresult rv; + nsCOMPtr ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("mStream doesn't QI to nsISeekableStream"); + return rv; + } + + int64_t absPos = 0; + switch (whence) { + case nsISeekableStream::NS_SEEK_SET: + absPos = offset; + break; + case nsISeekableStream::NS_SEEK_CUR: + absPos = mBufferStartOffset; + absPos += mCursor; + absPos += offset; + break; + case nsISeekableStream::NS_SEEK_END: + absPos = -1; + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus seek whence parameter"); + return NS_ERROR_UNEXPECTED; + } + + // Let mCursor point into the existing buffer if the new position is + // between the current cursor and the mFillPoint "fencepost" -- the + // client may never get around to a Read or Write after this Seek. + // Read and Write worry about flushing and filling in that event. + // But if we're at EOF, make sure to pass the seek through to the + // underlying stream, because it may have auto-closed itself and + // needs to reopen. + uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint && !mEOF) { + METER(bufstats.mSeeksWithinBuffer++); + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(bufstats.mSeeksOutsideBuffer++); + METER(bufstats.mBufferReadUponSeek += mCursor); + METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor); + rv = Flush(); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Flush returned error within nsBufferedStream::Seek, so we " + "exit early."); +#endif + return rv; + } + + rv = ras->Seek(whence, offset); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Error: ras->Seek() returned error within " + "nsBufferedStream::Seek, so we exit early."); +#endif + return rv; + } + + mEOF = false; + + // Recompute whether the offset we're seeking to is in our buffer. + // Note that we need to recompute because Flush() might have + // changed mBufferStartOffset. + offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint) { + // It's safe to just set mCursor to offsetInBuffer. In particular, we + // want to avoid calling Fill() here since we already have the data that + // was seeked to and calling Fill() might auto-close our underlying + // stream in some cases. + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex] + .mOldOffset = mBufferStartOffset + int64_t(mCursor)); + const int64_t minus1 = -1; + if (absPos == minus1) { + // then we had the SEEK_END case, above + int64_t tellPos; + rv = ras->Tell(&tellPos); + mBufferStartOffset = tellPos; + if (NS_FAILED(rv)) { + return rv; + } + } else { + mBufferStartOffset = absPos; + } + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex++] + .mNewOffset = mBufferStartOffset); + + mFillPoint = mCursor = 0; + + // If we seeked back to the start, then don't fill the buffer + // right now in case this is a lazily-opened file stream. + // We'll fill on the first read, like we did initially. + if (whence == nsISeekableStream::NS_SEEK_SET && offset == 0) { + return NS_OK; + } + return Fill(); +} + +NS_IMETHODIMP +nsBufferedStream::Tell(int64_t* result) { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + int64_t result64 = mBufferStartOffset; + result64 += mCursor; + *result = result64; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedStream::SetEOF() { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv; + nsCOMPtr ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ras->SetEOF(); + if (NS_SUCCEEDED(rv)) { + mEOF = true; + } + + return rv; +} + +nsresult nsBufferedStream::GetData(nsISupports** aResult) { + nsCOMPtr stream(mStream); + stream.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedInputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream) + +NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_BUFFEREDINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream) + // Unfortunately there isn't a macro that combines ambiguous and conditional, + // and as far as I can tell, no other class would need such a macro. + if (mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) { + foundInterface = + static_cast(static_cast(this)); + } else if (!mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) { + foundInterface = static_cast( + static_cast(this)); + } else + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mIsIPCSerializable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mIsAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mIsAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mIsCloneableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + mIsAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback, + mIsAsyncInputStreamLength) + NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, nsIInputStream, + nsIBufferedInputStream, nsISeekableStream, + nsITellableStream, nsIStreamBufferAccess) + +nsresult nsBufferedInputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr stream = new nsBufferedInputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) { + nsresult rv = nsBufferedStream::Init(stream, bufferSize); + NS_ENSURE_SUCCESS(rv, rv); + + { + nsCOMPtr stream = do_QueryInterface(mStream); + mIsIPCSerializable = !!stream; + } + + { + nsCOMPtr stream = do_QueryInterface(mStream); + mIsAsyncInputStream = !!stream; + } + + { + nsCOMPtr stream = do_QueryInterface(mStream); + mIsCloneableInputStream = !!stream; + } + + { + nsCOMPtr stream = do_QueryInterface(mStream); + mIsInputStreamLength = !!stream; + } + + { + nsCOMPtr stream = do_QueryInterface(mStream); + mIsAsyncInputStreamLength = !!stream; + } + + return NS_OK; +} + +already_AddRefed nsBufferedInputStream::GetInputStream() { + // A non-null mStream implies Init() has been called. + MOZ_ASSERT(mStream); + + nsIInputStream* out = nullptr; + DebugOnly rv = QueryInterface(NS_GET_IID(nsIInputStream), + reinterpret_cast(&out)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(out); + + return already_AddRefed(out); +} + +NS_IMETHODIMP +nsBufferedInputStream::Close() { + nsresult rv = NS_OK; + if (mStream) { + rv = Source()->Close(); + if (NS_FAILED(rv)) { + NS_WARNING( + "(debug) Error: Source()->Close() returned error in " + "bsBuffedInputStream::Close()."); + } + } + + nsBufferedStream::Close(); + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::Available(uint64_t* result) { + *result = 0; + + if (!mStream) { + return NS_OK; + } + + uint64_t avail = mFillPoint - mCursor; + + uint64_t tmp; + nsresult rv = Source()->Available(&tmp); + if (NS_SUCCEEDED(rv)) { + avail += tmp; + } + + if (avail) { + *result = avail; + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::StreamStatus() { + if (!mStream) { + return NS_OK; + } + + if (mFillPoint - mCursor) { + return NS_OK; + } + + return Source()->StreamStatus(); +} + +NS_IMETHODIMP +nsBufferedInputStream::Read(char* buf, uint32_t count, uint32_t* result) { + if (mBufferDisabled) { + if (!mStream) { + *result = 0; + return NS_OK; + } + nsresult rv = Source()->Read(buf, count, result); + if (NS_SUCCEEDED(rv)) { + mBufferStartOffset += *result; // so nsBufferedStream::Tell works + if (*result == 0) { + mEOF = true; + } + } + return rv; + } + + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* result) { + *result = 0; + + if (!mStream) { + return NS_OK; + } + + nsresult rv = NS_OK; + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t amt = std::min(count, mFillPoint - mCursor); + if (amt > 0) { + uint32_t read = 0; + rv = writer(static_cast(this), closure, + mBuffer + mCursor, *result, amt, &read); + if (NS_FAILED(rv)) { + // errors returned from the writer end here! + rv = NS_OK; + break; + } + *result += read; + count -= read; + mCursor += read; + } else { + rv = Fill(); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + break; + } + if (NS_FAILED(rv)) { + return rv; + } + if (mFillPoint == mCursor) { + break; + } + } + } + return (*result > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::IsNonBlocking(bool* aNonBlocking) { + if (mStream) { + return Source()->IsNonBlocking(aNonBlocking); + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsBufferedInputStream::Fill() { + if (mBufferDisabled) { + return NS_OK; + } + NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED); + + RecursiveMutexAutoLock lock(mBufferMutex); + + nsresult rv; + int32_t rem = int32_t(mFillPoint - mCursor); + if (rem > 0) { + // slide the remainder down to the start of the buffer + // |<------------->|<--rem-->|<--->| + // b c f s + memcpy(mBuffer, mBuffer + mCursor, rem); + } + mBufferStartOffset += mCursor; + mFillPoint = rem; + mCursor = 0; + + uint32_t amt; + rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt); + if (NS_FAILED(rv)) { + return rv; + } + + if (amt == 0) { + mEOF = true; + } + + mFillPoint += amt; + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) { + return nullptr; + } + + if (mBufferDisabled) { + return nullptr; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + char* buf = mBuffer + mCursor; + uint32_t rem = mFillPoint - mCursor; + if (rem == 0) { + if (NS_FAILED(Fill())) { + return nullptr; + } + buf = mBuffer + mCursor; + rem = mFillPoint - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) { + return nullptr; + } + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) { + return nullptr; + } + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) { + return; + } + + NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch"); + mCursor += aLength; +} + +NS_IMETHODIMP +nsBufferedInputStream::DisableBuffering() { + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) { + return NS_ERROR_UNEXPECTED; + } + + // Empty the buffer so nsBufferedStream::Tell works. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::EnableBuffering() { + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::GetUnbufferedStream(nsISupports** aStream) { + // Empty the buffer so subsequent i/o trumps any buffered data. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + + nsCOMPtr stream = mStream; + stream.forget(aStream); + return NS_OK; +} + +void nsBufferedInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + if (mStream) { + nsCOMPtr stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamHelper::SerializedComplexity(stream, aMaxSize, aSizeUsed, aPipes, + aTransferables); + } +} + +void nsBufferedInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + BufferedInputStreamParams params; + + if (mStream) { + nsCOMPtr stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamParams wrappedParams; + InputStreamHelper::SerializeInputStream(stream, wrappedParams, aMaxSize, + aSizeUsed); + + params.optionalStream().emplace(wrappedParams); + } + + params.bufferSize() = mBufferSize; + + aParams = params; +} + +bool nsBufferedInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const BufferedInputStreamParams& params = + aParams.get_BufferedInputStreamParams(); + const Maybe& wrappedParams = params.optionalStream(); + + nsCOMPtr stream; + if (wrappedParams.isSome()) { + stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref()); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + } + + nsresult rv = Init(stream, params.bufferSize()); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +NS_IMETHODIMP +nsBufferedInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +nsBufferedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + nsCOMPtr stream = do_QueryInterface(mStream); + if (!stream) { + // Stream is probably closed. Callback, if not nullptr, can be executed + // immediately + if (!aCallback) { + return NS_OK; + } + + if (aEventTarget) { + nsCOMPtr callable = NS_NewInputStreamReadyEvent( + "nsBufferedInputStream::OnInputStreamReady", aCallback, aEventTarget); + return callable->OnInputStreamReady(this); + } + + aCallback->OnInputStreamReady(this); + return NS_OK; + } + + nsCOMPtr callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return stream->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget); +} + +NS_IMETHODIMP +nsBufferedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr callback; + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +NS_IMETHODIMP +nsBufferedInputStream::GetData(nsIInputStream** aResult) { + nsCOMPtr stream; + nsBufferedStream::GetData(getter_AddRefs(stream)); + nsCOMPtr inputStream = do_QueryInterface(stream); + inputStream.forget(aResult); + return NS_OK; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +nsBufferedInputStream::GetCloneable(bool* aCloneable) { + *aCloneable = false; + + RecursiveMutexAutoLock lock(mBufferMutex); + + // If we don't have the buffer, the inputStream has been already closed. + // If mBufferStartOffset is not 0, the stream has been seeked or read. + // In both case the cloning is not supported. + if (!mBuffer || mBufferStartOffset) { + return NS_OK; + } + + nsCOMPtr stream = do_QueryInterface(mStream); + + // GetCloneable is infallible. + NS_ENSURE_TRUE(stream, NS_OK); + + return stream->GetCloneable(aCloneable); +} + +NS_IMETHODIMP +nsBufferedInputStream::Clone(nsIInputStream** aResult) { + RecursiveMutexAutoLock lock(mBufferMutex); + + if (!mBuffer || mBufferStartOffset) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr stream = do_QueryInterface(mStream); + NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); + + nsCOMPtr clonedStream; + nsresult rv = stream->Clone(getter_AddRefs(clonedStream)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr bis = new nsBufferedInputStream(); + rv = bis->Init(clonedStream, mBufferSize); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = + static_cast(bis.get())->GetInputStream().take(); + + return NS_OK; +} + +// nsIInputStreamLength + +NS_IMETHODIMP +nsBufferedInputStream::Length(int64_t* aLength) { + nsCOMPtr stream = do_QueryInterface(mStream); + NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); + + return stream->Length(aLength); +} + +// nsIAsyncInputStreamLength + +NS_IMETHODIMP +nsBufferedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + nsCOMPtr stream = do_QueryInterface(mStream); + if (!stream) { + // Stream is probably closed. Callback, if not nullptr, can be executed + // immediately + if (aCallback) { + const RefPtr self = this; + const nsCOMPtr callback = aCallback; + nsCOMPtr runnable = NS_NewRunnableFunction( + "nsBufferedInputStream::OnInputStreamLengthReady", + [self, callback] { callback->OnInputStreamLengthReady(self, -1); }); + + if (aEventTarget) { + aEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + } + return NS_OK; + } + + nsCOMPtr callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + mAsyncInputStreamLengthCallback = aCallback; + } + + MOZ_ASSERT(stream); + return stream->AsyncLengthWait(callback, aEventTarget); +} + +// nsIInputStreamLengthCallback + +NS_IMETHODIMP +nsBufferedInputStream::OnInputStreamLengthReady( + nsIAsyncInputStreamLength* aStream, int64_t aLength) { + nsCOMPtr callback; + { + MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncInputStreamLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncInputStreamLengthCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedOutputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream) +// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for +// non-nullness of mSafeStream. +NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIOutputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +nsresult nsBufferedOutputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr stream = new nsBufferedOutputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) { + // QI stream to an nsISafeOutputStream, to see if we should support it + mSafeStream = do_QueryInterface(stream); + + return nsBufferedStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsBufferedOutputStream::Close() { + if (!mStream) { + return NS_OK; + } + + nsresult rv1, rv2 = NS_OK; + + rv1 = Flush(); + +#ifdef DEBUG + if (NS_FAILED(rv1)) { + NS_WARNING( + "(debug) Flush() inside nsBufferedOutputStream::Close() returned error " + "(rv1)."); + } +#endif + + // If we fail to Flush all the data, then we close anyway and drop the + // remaining data in the buffer. We do this because it's what Unix does + // for fclose and close. However, we report the error from Flush anyway. + if (mStream) { + rv2 = Sink()->Close(); +#ifdef DEBUG + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) Sink->Close() inside nsBufferedOutputStream::Close() " + "returned error (rv2)."); + } +#endif + } + nsBufferedStream::Close(); + + if (NS_FAILED(rv1)) { + return rv1; + } + if (NS_FAILED(rv2)) { + return rv2; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::StreamStatus() { + return mStream ? Sink()->StreamStatus() : NS_BASE_STREAM_CLOSED; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = NS_OK; + uint32_t written = 0; + *result = 0; + if (!mStream) { + // We special case this situation. + // We should catch the failure, NS_BASE_STREAM_CLOSED ASAP, here. + // If we don't, eventually Flush() is called in the while loop below + // after so many writes. + // However, Flush() returns NS_OK when mStream is null (!!), + // and we don't get a meaningful error, NS_BASE_STREAM_CLOSED, + // soon enough when we use buffered output. +#ifdef DEBUG + NS_WARNING( + "(info) nsBufferedOutputStream::Write returns NS_BASE_STREAM_CLOSED " + "immediately (mStream==null)."); +#endif + return NS_BASE_STREAM_CLOSED; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t amt = std::min(count, mBufferSize - mCursor); + if (amt > 0) { + memcpy(mBuffer + mCursor, buf + written, amt); + written += amt; + count -= amt; + mCursor += amt; + if (mFillPoint < mCursor) mFillPoint = mCursor; + } else { + NS_ASSERTION(mFillPoint, "loop in nsBufferedOutputStream::Write!"); + rv = Flush(); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Flush() returned error in nsBufferedOutputStream::Write."); +#endif + break; + } + } + } + *result = written; + return (written > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Flush() { + nsresult rv; + uint32_t amt; + if (!mStream) { + // Stream already cancelled/flushed; probably because of previous error. + return NS_OK; + } + // optimize : some code within C-C needs to call Seek -> Flush() often. + if (mFillPoint == 0) { + return NS_OK; + } + RecursiveMutexAutoLock lock(mBufferMutex); + rv = Sink()->Write(mBuffer, mFillPoint, &amt); + if (NS_FAILED(rv)) { + return rv; + } + mBufferStartOffset += amt; + if (amt == mFillPoint) { + mFillPoint = mCursor = 0; + return NS_OK; // flushed everything + } + + // slide the remainder down to the start of the buffer + // |<-------------->|<---|----->| + // b a c s + uint32_t rem = mFillPoint - amt; + memmove(mBuffer, mBuffer + amt, rem); + mFillPoint = mCursor = rem; + return NS_ERROR_FAILURE; // didn't flush all +} + +// nsISafeOutputStream +NS_IMETHODIMP +nsBufferedOutputStream::Finish() { + // flush the stream, to write out any buffered data... + nsresult rv1 = nsBufferedOutputStream::Flush(); + nsresult rv2 = NS_OK; + + if (NS_FAILED(rv1)) { + NS_WARNING( + "(debug) nsBufferedOutputStream::Flush() failed in " + "nsBufferedOutputStream::Finish()! Possible dataloss."); + + rv2 = Sink()->Close(); + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) Sink()->Close() failed in nsBufferedOutputStream::Finish()! " + "Possible dataloss."); + } + } else { + rv2 = mSafeStream->Finish(); + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) mSafeStream->Finish() failed within " + "nsBufferedOutputStream::Flush()! Possible dataloss."); + } + } + + // ... and close the buffered stream, so any further attempts to flush/close + // the buffered stream won't cause errors. + nsBufferedStream::Close(); + + // We want to return the errors precisely from Finish() + // and mimick the existing error handling in + // nsBufferedOutputStream::Close() as reference. + + if (NS_FAILED(rv1)) { + return rv1; + } + if (NS_FAILED(rv2)) { + return rv2; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteFrom(nsIInputStream* inStr, uint32_t count, + uint32_t* _retval) { + return WriteSegments(NS_CopyStreamToSegment, inStr, count, _retval); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure, + uint32_t count, uint32_t* _retval) { + *_retval = 0; + nsresult rv; + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t left = std::min(count, mBufferSize - mCursor); + if (left == 0) { + rv = Flush(); + if (NS_FAILED(rv)) { + return (*_retval > 0) ? NS_OK : rv; + } + + continue; + } + + uint32_t read = 0; + rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read); + + if (NS_FAILED(rv)) { // If we have read some data, return ok + return (*_retval > 0) ? NS_OK : rv; + } + mCursor += read; + *_retval += read; + count -= read; + mFillPoint = std::max(mFillPoint, mCursor); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::IsNonBlocking(bool* aNonBlocking) { + if (mStream) { + return Sink()->IsNonBlocking(aNonBlocking); + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP_(char*) +nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) { + return nullptr; + } + + if (mBufferDisabled) { + return nullptr; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + char* buf = mBuffer + mCursor; + uint32_t rem = mBufferSize - mCursor; + if (rem == 0) { + if (NS_FAILED(Flush())) { + return nullptr; + } + buf = mBuffer + mCursor; + rem = mBufferSize - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) { + return nullptr; + } + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) { + return nullptr; + } + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) { + return; + } + + NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch"); + mCursor += aLength; + if (mFillPoint < mCursor) { + mFillPoint = mCursor; + } +} + +NS_IMETHODIMP +nsBufferedOutputStream::DisableBuffering() { + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) { + return NS_ERROR_UNEXPECTED; + } + + // Empty the buffer so nsBufferedStream::Tell works. + nsresult rv = Flush(); + if (NS_FAILED(rv)) { + return rv; + } + + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::EnableBuffering() { + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::GetUnbufferedStream(nsISupports** aStream) { + // Empty the buffer so subsequent i/o trumps any buffered data. + if (mFillPoint) { + nsresult rv = Flush(); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsCOMPtr stream = mStream; + stream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::GetData(nsIOutputStream** aResult) { + nsCOMPtr stream; + nsBufferedStream::GetData(getter_AddRefs(stream)); + nsCOMPtr outputStream = do_QueryInterface(stream); + outputStream.forget(aResult); + return NS_OK; +} +#undef METER + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h new file mode 100644 index 0000000000..5c5c7c07e7 --- /dev/null +++ b/netwerk/base/nsBufferedStreams.h @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBufferedStreams_h__ +#define nsBufferedStreams_h__ + +#include "nsIBufferedStreams.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsIStreamBufferAccess.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStreamLength.h" +#include "mozilla/Mutex.h" +#include "mozilla/RecursiveMutex.h" + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedStream : public nsISeekableStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + + nsBufferedStream() = default; + + void Close(); + + protected: + virtual ~nsBufferedStream(); + + nsresult Init(nsISupports* stream, uint32_t bufferSize); + nsresult GetData(nsISupports** aResult); + NS_IMETHOD Fill() = 0; + NS_IMETHOD Flush() = 0; + + uint32_t mBufferSize{0}; + char* mBuffer MOZ_GUARDED_BY(mBufferMutex){nullptr}; + + mozilla::RecursiveMutex mBufferMutex{"nsBufferedStream::mBufferMutex"}; + + // mBufferStartOffset is the offset relative to the start of mStream. + int64_t mBufferStartOffset{0}; + + // mCursor is the read cursor for input streams, or write cursor for + // output streams, and is relative to mBufferStartOffset. + uint32_t mCursor{0}; + + // mFillPoint is the amount available in the buffer for input streams, + // or the high watermark of bytes written into the buffer, and therefore + // is relative to mBufferStartOffset. + uint32_t mFillPoint{0}; + + nsCOMPtr mStream; // cast to appropriate subclass + + bool mBufferDisabled{false}; + bool mEOF{false}; // True if mStream is at EOF + bool mSeekable{true}; + uint8_t mGetBufferCount{0}; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedInputStream final : public nsBufferedStream, + public nsIBufferedInputStream, + public nsIStreamBufferAccess, + public nsIIPCSerializableInputStream, + public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsICloneableInputStream, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + nsBufferedInputStream() : nsBufferedStream() {} + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsIInputStream* Source() { return (nsIInputStream*)mStream.get(); } + + /** + * If there's a reference/pointer to an nsBufferedInputStream BEFORE calling + * Init() AND the intent is to ultimately convert/assign that + * reference/pointer to an nsIInputStream, DO NOT use that initial + * reference/pointer. Instead, use the value of QueryInterface-ing to an + * nsIInputStream (and, again, the QueryInterface must be performed after + * Init()). This is because nsBufferedInputStream has multiple underlying + * nsIInputStreams (one from nsIBufferedInputStream and one from + * nsIAsyncInputStream), and the correct base nsIInputStream to use will be + * unknown until the final value of mIsAsyncInputStream is set in Init(). + * + * This method, however, does just that but also hides the QI details and + * will assert if called before Init(). + */ + already_AddRefed GetInputStream(); + + protected: + virtual ~nsBufferedInputStream() = default; + + NS_IMETHOD Fill() override; + NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams + + mozilla::Mutex mMutex MOZ_UNANNOTATED{"nsBufferedInputStream::mMutex"}; + + // This value is protected by mutex. + nsCOMPtr mAsyncWaitCallback; + + // This value is protected by mutex. + nsCOMPtr mAsyncInputStreamLengthCallback; + + bool mIsIPCSerializable{true}; + bool mIsAsyncInputStream{false}; + bool mIsCloneableInputStream{false}; + bool mIsInputStreamLength{false}; + bool mIsAsyncInputStreamLength{false}; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedOutputStream : public nsBufferedStream, + public nsISafeOutputStream, + public nsIBufferedOutputStream, + public nsIStreamBufferAccess { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSISAFEOUTPUTSTREAM + NS_DECL_NSIBUFFEREDOUTPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + + nsBufferedOutputStream() : nsBufferedStream() {} + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsIOutputStream* Sink() { return (nsIOutputStream*)mStream.get(); } + + protected: + virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); } + + NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams + + nsCOMPtr mSafeStream; // QI'd from mStream +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsBufferedStreams_h__ diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp new file mode 100644 index 0000000000..4c5a3a0fd6 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDNSPrefetch.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +#include "nsIDNSAdditionalInfo.h" +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsIDNSByTypeRecord.h" +#include "nsICancelable.h" +#include "nsIURI.h" +#include "mozilla/Atomics.h" +#include "mozilla/Preferences.h" + +static mozilla::StaticRefPtr sDNSService; + +nsresult nsDNSPrefetch::Initialize(nsIDNSService* aDNSService) { + MOZ_ASSERT(NS_IsMainThread()); + + sDNSService = aDNSService; + return NS_OK; +} + +nsresult nsDNSPrefetch::Shutdown() { + sDNSService = nullptr; + return NS_OK; +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI, + mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode, + nsIDNSListener* aListener, bool storeTiming) + : mOriginAttributes(aOriginAttributes), + mStoreTiming(storeTiming), + mTRRMode(aTRRMode), + mListener(do_GetWeakReference(aListener)) { + aURI->GetAsciiHost(mHostname); + aURI->GetPort(&mPort); +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI, + mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode) + : mOriginAttributes(aOriginAttributes), + mStoreTiming(false), + mTRRMode(aTRRMode), + mListener(nullptr) { + aURI->GetAsciiHost(mHostname); +} + +nsresult nsDNSPrefetch::Prefetch(nsIDNSService::DNSFlags flags) { + if (mHostname.IsEmpty()) return NS_ERROR_NOT_AVAILABLE; + + if (!sDNSService) return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr 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 target = mozilla::GetCurrentSerialEventTarget(); + + flags |= nsIDNSService::GetFlagsFromTRRMode(mTRRMode); + + return sDNSService->AsyncResolveNative( + mHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, + flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this, target, + mOriginAttributes, getter_AddRefs(tmpOutstanding)); +} + +nsresult nsDNSPrefetch::PrefetchLow(nsIDNSService::DNSFlags aFlags) { + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | aFlags); +} + +nsresult nsDNSPrefetch::PrefetchMedium(nsIDNSService::DNSFlags aFlags) { + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | aFlags); +} + +nsresult nsDNSPrefetch::PrefetchHigh(nsIDNSService::DNSFlags aFlags) { + return Prefetch(aFlags); +} + +namespace { + +class HTTPSRRListener final : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + explicit HTTPSRRListener( + std::function&& aCallback) + : mResultCallback(std::move(aCallback)) {} + + private: + ~HTTPSRRListener() = default; + std::function mResultCallback; +}; + +NS_IMPL_ISUPPORTS(HTTPSRRListener, nsIDNSListener) + +NS_IMETHODIMP +HTTPSRRListener::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec, + nsresult aStatus) { + if (NS_FAILED(aStatus)) { + mResultCallback(nullptr); + return NS_OK; + } + + nsCOMPtr httpsRecord = do_QueryInterface(aRec); + mResultCallback(httpsRecord); + return NS_OK; +} + +}; // namespace + +nsresult nsDNSPrefetch::FetchHTTPSSVC( + bool aRefreshDNS, bool aPrefetch, + std::function&& aCallback) { + if (!sDNSService) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr target = mozilla::GetCurrentSerialEventTarget(); + nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(mTRRMode); + if (aRefreshDNS) { + flags |= nsIDNSService::RESOLVE_BYPASS_CACHE; + } + if (aPrefetch) { + flags |= nsIDNSService::RESOLVE_SPECULATE; + } + + nsCOMPtr tmpOutstanding; + nsCOMPtr listener = new HTTPSRRListener(std::move(aCallback)); + nsCOMPtr info; + if (mPort != -1) { + sDNSService->NewAdditionalInfo(""_ns, mPort, getter_AddRefs(info)); + } + return sDNSService->AsyncResolveNative( + mHostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info, listener, + target, mOriginAttributes, getter_AddRefs(tmpOutstanding)); +} + +NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener) + +NS_IMETHODIMP +nsDNSPrefetch::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec, + nsresult status) { + if (mStoreTiming) { + mEndTimestamp = mozilla::TimeStamp::Now(); + } + nsCOMPtr listener = do_QueryReferent(mListener); + if (listener) { + listener->OnLookupComplete(request, rec, status); + } + + return NS_OK; +} diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h new file mode 100644 index 0000000000..7cad00b588 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDNSPrefetch_h___ +#define nsDNSPrefetch_h___ + +#include + +#include "nsIWeakReferenceUtils.h" +#include "nsString.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" + +#include "nsIDNSListener.h" +#include "nsIRequest.h" +#include "nsIDNSService.h" + +class nsIURI; +class nsIDNSHTTPSSVCRecord; + +class nsDNSPrefetch final : public nsIDNSListener { + ~nsDNSPrefetch() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode, nsIDNSListener* aListener, + bool storeTiming); + // For fetching HTTPS RR. + nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode); + bool TimingsValid() const { + return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull(); + } + // Only use the two timings if TimingsValid() returns true + const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; } + const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; } + + static nsresult Initialize(nsIDNSService* aDNSService); + static nsresult Shutdown(); + + // Call one of the following methods to start the Prefetch. + nsresult PrefetchHigh( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + nsresult PrefetchMedium( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + nsresult PrefetchLow( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + + nsresult FetchHTTPSSVC( + bool aRefreshDNS, bool aPrefetch, + std::function&& aCallback); + + private: + nsCString mHostname; + int32_t mPort{-1}; + mozilla::OriginAttributes mOriginAttributes; + bool mStoreTiming; + nsIRequest::TRRMode mTRRMode; + mozilla::TimeStamp mStartTimestamp; + mozilla::TimeStamp mEndTimestamp; + nsWeakPtr mListener; + + nsresult Prefetch(nsIDNSService::DNSFlags flags); +}; + +#endif diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp new file mode 100644 index 0000000000..51f30a2fca --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set sw=2 sts=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + The converts a filesystem directory into an "HTTP index" stream per + Lou Montulli's original spec: + + http://www.mozilla.org/projects/netlib/dirindexformat.html + + */ + +#include "nsEscape.h" +#include "nsDirectoryIndexStream.h" +#include "mozilla/Logging.h" +#include "prtime.h" +#include "nsIFile.h" +#include "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. + +using namespace mozilla; +static LazyLogModule gLog("nsDirectoryIndexStream"); + +nsDirectoryIndexStream::nsDirectoryIndexStream() { + MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this)); +} + +static int compare(nsIFile* aElement1, nsIFile* aElement2, 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; + MOZ_ASSERT(isDir, "not a directory"); + if (!isDir) return NS_ERROR_ILLEGAL_VALUE; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: initialized on %s", this, + aDir->HumanReadablePath().get())); + } + + // Sigh. We have to allocate on the heap because there are no + // assignment operators defined. + nsCOMPtr iter; + rv = aDir->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) return rv; + + // Now lets sort, because clients expect it that way + // XXX - should we do so here, or when the first item is requested? + // XXX - use insertion sort instead? + + nsCOMPtr file; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) { + mArray.AppendObject(file); // addrefs + } + + mArray.Sort(compare, nullptr); + + 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 result = new nsDirectoryIndexStream(); + if (!result) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = result->Init(aDir); + if (NS_FAILED(rv)) { + return rv; + } + + result.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream) + +// The below routines are proxied to the UI thread! +NS_IMETHODIMP +nsDirectoryIndexStream::Close() { + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::Available(uint64_t* aLength) { + if (NS_FAILED(mStatus)) return mStatus; + + // If there's data in our buffer, use that + if (mOffset < (int32_t)mBuf.Length()) { + *aLength = mBuf.Length() - mOffset; + return NS_OK; + } + + // Returning one byte is not ideal, but good enough + *aLength = (mPos < mArray.Count()) ? 1 : 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::StreamStatus() { return mStatus; } + +NS_IMETHODIMP +nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aReadCount = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) return mStatus; + + uint32_t nread = 0; + + // If anything is enqueued (or left-over) in mBuf, then feed it to + // the reader first. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + + // Room left? + if (aCount > 0) { + mOffset = 0; + mBuf.Truncate(); + + // Okay, now we'll suck stuff off of our iterator into the mBuf... + while (uint32_t(mBuf.Length()) < aCount) { + bool more = mPos < mArray.Count(); + if (!more) break; + + // don't addref, for speed - an addref happened when it + // was placed in the array, so it's not going to go stale + nsIFile* current = mArray.ObjectAt(mPos); + ++mPos; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: iterated %s", this, + current->HumanReadablePath().get())); + } + + // rjc: don't return hidden files/directories! + // bbaetz: why not? + nsresult rv; +#ifndef XP_UNIX + bool hidden = false; + current->IsHidden(&hidden); + if (hidden) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: skipping hidden file/directory", + this)); + continue; + } +#endif + + int64_t fileSize = 0; + current->GetFileSize(&fileSize); + + PRTime fileInfoModifyTime = 0; + current->GetLastModifiedTime(&fileInfoModifyTime); + fileInfoModifyTime *= PR_USEC_PER_MSEC; + + mBuf.AppendLiteral("201: "); + + // The "filename" field + if (!NS_IsNativeUTF8()) { + nsAutoString leafname; + rv = current->GetLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && + NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } else { + nsAutoCString leafname; + rv = current->GetNativeLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && NS_Escape(leafname, escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } + + // The "content-length" field + mBuf.AppendInt(fileSize, 10); + mBuf.Append(' '); + + // The "last-modified" field + PRExplodedTime tm; + PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm); + { + char buf[64]; + PR_FormatTimeUSEnglish( + buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); + mBuf.Append(buf); + } + + // The "file-type" field + bool isFile = true; + current->IsFile(&isFile); + if (isFile) { + mBuf.AppendLiteral("FILE "); + } else { + bool isDir; + rv = current->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + if (isDir) { + mBuf.AppendLiteral("DIRECTORY "); + } else { + bool isLink; + rv = current->IsSymlink(&isLink); + if (NS_FAILED(rv)) return rv; + if (isLink) { + mBuf.AppendLiteral("SYMBOLIC-LINK "); + } + } + } + + mBuf.Append('\n'); + } + + // ...and once we've either run out of directory entries, or + // filled up the buffer, then we'll push it to the reader. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + } + + *aReadCount = nread; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h new file mode 100644 index 0000000000..6bdebc7cfe --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDirectoryIndexStream_h__ +#define nsDirectoryIndexStream_h__ + +#include "mozilla/Attributes.h" + +#include "nsString.h" +#include "nsIInputStream.h" +#include "nsCOMArray.h" + +class nsIFile; + +class nsDirectoryIndexStream final : public nsIInputStream { + private: + nsCString mBuf; + int32_t mOffset{0}; + nsresult mStatus{NS_OK}; + + int32_t mPos{0}; // position within mArray + nsCOMArray mArray; // file objects within the directory + + nsDirectoryIndexStream(); + /** + * aDir will only be used on the calling thread. + */ + nsresult Init(nsIFile* aDir); + ~nsDirectoryIndexStream(); + + public: + /** + * aDir will only be used on the calling thread. + */ + static nsresult Create(nsIFile* aDir, nsIInputStream** aResult); + + // nsISupportsInterface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIInputStream interface + NS_DECL_NSIINPUTSTREAM +}; + +#endif // nsDirectoryIndexStream_h__ diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp new file mode 100644 index 0000000000..b6d3240226 --- /dev/null +++ b/netwerk/base/nsDownloader.cpp @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDownloader.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsNetUtil.h" +#include "nsCRTGlue.h" + +nsDownloader::~nsDownloader() { + if (mLocation && mLocationIsTemp) { + // release the sink first since it may still hold an open file + // descriptor to mLocation. this needs to happen before the + // file can be removed otherwise the Remove call will fail. + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + nsresult rv = mLocation->Remove(false); + if (NS_FAILED(rv)) NS_ERROR("unable to remove temp file"); + } +} + +NS_IMPL_ISUPPORTS(nsDownloader, nsIDownloader, nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +nsDownloader::Init(nsIDownloadObserver* observer, nsIFile* location) { + mObserver = observer; + mLocation = location; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStartRequest(nsIRequest* request) { + nsresult rv; + if (!mLocation) { + nsCOMPtr location; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location)); + if (NS_FAILED(rv)) return rv; + + char buf[13]; + NS_MakeRandomString(buf, 8); + memcpy(buf + 8, ".tmp", 5); + rv = location->AppendNative(nsDependentCString(buf, 12)); + if (NS_FAILED(rv)) return rv; + + rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) return rv; + + location.swap(mLocation); + mLocationIsTemp = true; + } + + rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation); + if (NS_FAILED(rv)) return rv; + + // we could wrap this output stream with a buffered output stream, + // but it shouldn't be necessary since we will be writing large + // chunks given to us via OnDataAvailable. + + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStopRequest(nsIRequest* request, nsresult status) { + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + mObserver->OnDownloadComplete(this, request, status, mLocation); + mObserver = nullptr; + + return NS_OK; +} + +nsresult nsDownloader::ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, + uint32_t toOffset, uint32_t count, + uint32_t* writeCount) { + nsDownloader* self = (nsDownloader*)closure; + if (self->mSink) return self->mSink->Write(fromRawSegment, count, writeCount); + + *writeCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + uint32_t n; + return inStr->ReadSegments(ConsumeData, this, count, &n); +} diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h new file mode 100644 index 0000000000..fe95dd6ac2 --- /dev/null +++ b/netwerk/base/nsDownloader.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDownloader_h__ +#define nsDownloader_h__ + +#include "nsIDownloader.h" +#include "nsCOMPtr.h" + +class nsIFile; +class nsIOutputStream; + +class nsDownloader : public nsIDownloader { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOWNLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsDownloader() = default; + + protected: + virtual ~nsDownloader(); + + static nsresult ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, uint32_t toOffset, + uint32_t count, uint32_t* writeCount); + + nsCOMPtr mObserver; + nsCOMPtr mLocation; + nsCOMPtr mSink; + bool mLocationIsTemp{false}; +}; + +#endif // nsDownloader_h__ diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp new file mode 100644 index 0000000000..32281f6d02 --- /dev/null +++ b/netwerk/base/nsFileStreams.cpp @@ -0,0 +1,1031 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ipc/IPCMessageUtils.h" + +#if defined(XP_UNIX) +# include +#elif defined(XP_WIN) +# include +# include "nsILocalFileWin.h" +#else +// XXX add necessary include file for ftruncate (or equivalent) +#endif + +#include "private/pprio.h" +#include "prerror.h" + +#include "IOActivityMonitor.h" +#include "nsFileStreams.h" +#include "nsIFile.h" +#include "nsReadLine.h" +#include "nsIClassInfoImpl.h" +#include "nsLiteralString.h" +#include "nsSocketTransport2.h" // for ErrorAccordingToNSPR() +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/RandomAccessStreamParams.h" +#include "mozilla/Unused.h" +#include "mozilla/FileUtils.h" +#include "mozilla/UniquePtr.h" +#include "nsNetCID.h" +#include "nsXULAppAPI.h" + +using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType; + +using namespace mozilla::ipc; +using namespace mozilla::net; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStreamBase + +nsFileStreamBase::~nsFileStreamBase() { + // We don't want to try to rewrind the stream when shutting down. + mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND; + + Close(); +} + +NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream, + nsIFileMetadata) + +NS_IMETHODIMP +nsFileStreamBase::Seek(int32_t whence, int64_t offset) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::Tell(int64_t* result) { + if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) { + *result = 0; + return NS_OK; + } + + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::SetEOF() { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + +#if defined(XP_UNIX) + // Some system calls require an EOF offset. + int64_t offset; + rv = Tell(&offset); + if (NS_FAILED(rv)) return rv; +#endif + +#if defined(XP_UNIX) + if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } +#else + // XXX not implemented +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetSize(int64_t* _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + *_retval = int64_t(info.size); + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetLastModified(int64_t* _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *_retval = 0; + } else { + *_retval = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = mFD; + return NS_OK; +} + +nsresult nsFileStreamBase::Close() { + if (mState == eClosed) { + return NS_OK; + } + + CleanUpOpen(); + + nsresult rv = NS_OK; + if (mFD) { + if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR; + mFD = nullptr; + mState = eClosed; + } + return rv; +} + +nsresult nsFileStreamBase::Available(uint64_t* aResult) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + // PR_Available with files over 4GB returns an error, so we have to + // use the 64-bit version of PR_Available. + int64_t avail = PR_Available64(mFD); + if (avail == -1) { + return NS_ErrorAccordingToNSPR(); + } + + // If available is greater than 4GB, return 4GB + *aResult = (uint64_t)avail; + return NS_OK; +} + +nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount, + uint32_t* aResult) { + nsresult rv = DoPendingOpen(); + if (rv == NS_BASE_STREAM_CLOSED) { + *aResult = 0; + return NS_OK; + } + + if (NS_FAILED(rv)) { + return rv; + } + + int32_t bytesRead = PR_Read(mFD, aBuf, aCount); + if (bytesRead == -1) { + return NS_ErrorAccordingToNSPR(); + } + + *aResult = bytesRead; + return NS_OK; +} + +nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + // ReadSegments is not implemented because it would be inefficient when + // the writer does not consume all data. If you want to call ReadSegments, + // wrap a BufferedInputStream around the file stream. That will call + // Read(). + + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} + +nsresult nsFileStreamBase::Flush(void) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cnt = PR_Sync(mFD); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +nsresult nsFileStreamBase::StreamStatus() { + switch (mState) { + case eUnitialized: + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + + case eDeferredOpen: + return NS_OK; + + case eOpened: + MOZ_ASSERT(mFD); + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_FAILURE; + } + return NS_OK; + + case eClosed: + MOZ_ASSERT(!mFD); + return NS_BASE_STREAM_CLOSED; + + case eError: + return mErrorValue; + } + + MOZ_CRASH("Invalid mState value."); + return NS_ERROR_FAILURE; +} + +nsresult nsFileStreamBase::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cnt = PR_Write(mFD, buf, count); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count, + uint32_t* _retval) { + MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)"); + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure, + uint32_t count, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags, + int32_t aPerm, bool aDeferred) { + NS_ENSURE_STATE(aFile); + + mOpenParams.ioFlags = aIoFlags; + mOpenParams.perm = aPerm; + + if (aDeferred) { + // Clone the file, as it may change between now and the deferred open + nsCOMPtr file; + nsresult rv = aFile->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + mOpenParams.localFile = std::move(file); + NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED); + + mState = eDeferredOpen; + return NS_OK; + } + + mOpenParams.localFile = aFile; + + // Following call open() at main thread. + // Main thread might be blocked, while open a remote file. + return DoOpen(); +} + +void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; } + +nsresult nsFileStreamBase::DoOpen() { + MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized || + mState == eClosed); + NS_ASSERTION(!mFD, "Already have a file descriptor!"); + NS_ASSERTION(mOpenParams.localFile, "Must have a file to open"); + + PRFileDesc* fd; + nsresult rv; + + if (mOpenParams.ioFlags & PR_CREATE_FILE) { + nsCOMPtr parent; + mOpenParams.localFile->GetParent(getter_AddRefs(parent)); + + // Result doesn't need to be checked. If the file's parent path does not + // exist, make it. If it does exist, do nothing. + if (parent) { + mozilla::Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755); + } + } + +#ifdef XP_WIN + if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) { + nsCOMPtr file = do_QueryInterface(mOpenParams.localFile); + MOZ_ASSERT(file); + + rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags, + mOpenParams.perm, &fd); + } else +#endif // XP_WIN + { + rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags, + mOpenParams.perm, &fd); + } + + if (rv == NS_OK && IOActivityMonitor::IsActive()) { + auto nativePath = mOpenParams.localFile->NativePath(); + if (!nativePath.IsEmpty()) { +// registering the file to the activity monitor +#ifdef XP_WIN + // 16 bits unicode + IOActivityMonitor::MonitorFile( + fd, NS_ConvertUTF16toUTF8(nativePath.get()).get()); +#else + // 8 bit unicode + IOActivityMonitor::MonitorFile(fd, nativePath.get()); +#endif + } + } + + CleanUpOpen(); + + if (NS_FAILED(rv)) { + mState = eError; + mErrorValue = rv; + return rv; + } + + mFD = fd; + mState = eOpened; + + return NS_OK; +} + +nsresult nsFileStreamBase::DoPendingOpen() { + switch (mState) { + case eUnitialized: + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + + case eDeferredOpen: + return DoOpen(); + + case eOpened: + MOZ_ASSERT(mFD); + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_FAILURE; + } + return NS_OK; + + case eClosed: + MOZ_ASSERT(!mFD); + return NS_BASE_STREAM_CLOSED; + + case eError: + return mErrorValue; + } + + MOZ_CRASH("Invalid mState value."); + return NS_ERROR_FAILURE; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileInputStream + +NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase) +NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase) + +NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_LOCALFILEINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsILineInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsFileInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable()) +NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream, + nsIFileInputStream, nsISeekableStream, + nsITellableStream, nsILineInputStream) + +nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr stream = new nsFileInputStream(); + return stream->QueryInterface(aIID, aResult); +} + +nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm) { + nsresult rv = NS_OK; + + // If the previous file is open, close it + if (mFD) { + rv = Close(); + if (NS_FAILED(rv)) return rv; + } + + // Open the file + if (aIOFlags == -1) aIOFlags = PR_RDONLY; + if (aPerm == -1) aPerm = 0; + + return MaybeOpen(aFile, aIOFlags, aPerm, + mBehaviorFlags & nsIFileInputStream::DEFER_OPEN); +} + +NS_IMETHODIMP +nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) { + NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = aBehaviorFlags; + mState = eUnitialized; + + mFile = aFile; + mIOFlags = aIOFlags; + mPerm = aPerm; + + return Open(aFile, aIOFlags, aPerm); +} + +NS_IMETHODIMP +nsFileInputStream::Close() { + // If this stream has already been closed, do nothing. + if (mState == eClosed) { + return NS_OK; + } + + // Get the cache position at the time the file was close. This allows + // NS_SEEK_CUR on a closed file that has been opened with + // REOPEN_ON_REWIND. + if (mBehaviorFlags & REOPEN_ON_REWIND) { + // Get actual position. Not one modified by subclasses + nsFileStreamBase::Tell(&mCachedPosition); + } + + // explicitly clear mLineBuffer in case this stream is reopened + mLineBuffer = nullptr; + return nsFileStreamBase::Close(); +} + +NS_IMETHODIMP +nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) { + nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + // Don't warn if this is a deffered file not found. + return rv; + } + + if (NS_FAILED(rv)) { + return rv; + } + + // Check if we're at the end of file and need to close + if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) { + Close(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) { + if (!mLineBuffer) { + mLineBuffer = mozilla::MakeUnique>(); + } + return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) { + return SeekInternal(aWhence, aOffset); +} + +nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset, + bool aClearBuf) { + nsresult rv = DoPendingOpen(); + if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) { + return rv; + } + + if (aClearBuf) { + mLineBuffer = nullptr; + } + + if (rv == NS_BASE_STREAM_CLOSED) { + if (mBehaviorFlags & REOPEN_ON_REWIND) { + rv = Open(mFile, mIOFlags, mPerm); + NS_ENSURE_SUCCESS(rv, rv); + + // If the file was closed, and we do a relative seek, use the + // position we cached when we closed the file to seek to the right + // location. + if (aWhence == NS_SEEK_CUR) { + aWhence = NS_SEEK_SET; + aOffset += mCachedPosition; + } + // If we're trying to seek to the start then we're done, so + // return early to avoid Seek from calling DoPendingOpen and + // opening the underlying file earlier than necessary. + if (aWhence == NS_SEEK_SET && aOffset == 0) { + return NS_OK; + } + } else { + return NS_BASE_STREAM_CLOSED; + } + } + + return nsFileStreamBase::Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +nsFileInputStream::Tell(int64_t* aResult) { + return nsFileStreamBase::Tell(aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Available(uint64_t* aResult) { + return nsFileStreamBase::Available(aResult); +} + +NS_IMETHODIMP +nsFileInputStream::StreamStatus() { return nsFileStreamBase::StreamStatus(); } + +void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + *aTransferables = 1; +} + +void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + FileInputStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen())) { + MOZ_ASSERT(mFD); + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + NS_ASSERTION(fd, "This should never be null!"); + + params.fileDescriptor() = FileDescriptor(fd); + + Close(); + } else { + NS_WARNING( + "This file has not been opened (or could not be opened). " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptor() = FileDescriptor(); + } + + int32_t behaviorFlags = mBehaviorFlags; + + // The receiving process (or thread) is going to have an open file + // descriptor automatically so transferring this flag is meaningless. + behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN; + + params.behaviorFlags() = behaviorFlags; + params.ioFlags() = mIOFlags; + + aParams = params; +} + +bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) { + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!"); + NS_ASSERTION(!mFile, "Should never have a file here!"); + NS_ASSERTION(!mPerm, "This should always be 0!"); + + if (aParams.type() != InputStreamParams::TFileInputStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const FileInputStreamParams& params = aParams.get_FileInputStreamParams(); + + const FileDescriptor& fd = params.fileDescriptor(); + + if (fd.IsValid()) { + auto rawFD = fd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return false; + } + mFD = fileDesc; + mState = eOpened; + } else { + NS_WARNING("Received an invalid file descriptor!"); + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + mBehaviorFlags = params.behaviorFlags(); + + if (!XRE_IsParentProcess()) { + // A child process shouldn't close when it reads the end because it will + // not be able to reopen the file later. + mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF; + + // A child process will not be able to reopen the file so this flag is + // meaningless. + mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND; + } + + mIOFlags = params.ioFlags(); + + return true; +} + +bool nsFileInputStream::IsCloneable() const { + // This inputStream is cloneable only if has been created using Init() and + // it owns a nsIFile. This is not true when it is deserialized from IPC. + return XRE_IsParentProcess() && mFile; +} + +NS_IMETHODIMP +nsFileInputStream::GetCloneable(bool* aCloneable) { + *aCloneable = IsCloneable(); + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::Clone(nsIInputStream** aResult) { + MOZ_ASSERT(IsCloneable()); + return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm, + mBehaviorFlags); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase, + nsIOutputStream, nsIFileOutputStream) + +nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr stream = new nsFileOutputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + mState = eUnitialized; + + if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + if (perm <= 0) perm = 0664; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN); +} + +nsresult nsFileOutputStream::InitWithFileDescriptor( + const mozilla::ipc::FileDescriptor& aFd) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + if (aFd.IsValid()) { + auto rawFD = aFd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return NS_ERROR_FAILURE; + } + mFD = fileDesc; + mState = eOpened; + } else { + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileOutputStream::Preallocate(int64_t aLength) { + if (!mFD) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!mozilla::fallocate(mFD, aLength)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAtomicFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream, + nsISafeOutputStream, nsIOutputStream, + nsIFileOutputStream) + +NS_IMETHODIMP +nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter + // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending + // to existing file. So, throw an exception only if `PR_APPEND` is + // explicitly specified without `PR_TRUNCATE`. + if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) { + return NS_ERROR_INVALID_ARG; + } + return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags); +} + +nsresult nsAtomicFileOutputStream::DoOpen() { + // Make sure mOpenParams.localFile will be empty if we bail somewhere in + // this function + nsCOMPtr 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 tempResult; + rv = file->Clone(getter_AddRefs(tempResult)); + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + tempResult->Normalize(); + } + + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + // Abort if |file| is not writable; it won't work as an output stream. + bool isWritable; + if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + uint32_t origPerm; + if (NS_FAILED(file->GetPermissions(&origPerm))) { + NS_ERROR("Can't get permissions of target file"); + origPerm = mOpenParams.perm; + } + + // XXX What if |perm| is more restrictive then |origPerm|? + // This leaves the user supplied permissions as they were. + rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm); + } + if (NS_SUCCEEDED(rv)) { + // nsFileOutputStream::DoOpen will work on the temporary file, so we + // prepare it and place it in mOpenParams.localFile. + mOpenParams.localFile = tempResult; + mTempFile = tempResult; + mTargetFile = file; + rv = nsFileOutputStream::DoOpen(); + } + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Close() { + nsresult rv = nsFileOutputStream::Close(); + + // the consumer doesn't want the original file overwritten - + // so clean up by removing the temp file. + if (mTempFile) { + mTempFile->Remove(false); + mTempFile = nullptr; + } + + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Finish() { + nsresult rv = nsFileOutputStream::Close(); + + // if there is no temp file, don't try to move it over the original target. + // It would destroy the targetfile if close() is called twice. + if (!mTempFile) return rv; + + // Only overwrite if everything was ok, and the temp file could be closed. + if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) { + NS_ENSURE_STATE(mTargetFile); + + if (!mTargetFileExists) { + // If the target file did not exist when we were initialized, then the + // temp file we gave out was actually a reference to the target file. + // since we succeeded in writing to the temp file (and hence succeeded + // in writing to the target file), there is nothing more to do. +#ifdef DEBUG + bool equal; + if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) { + NS_WARNING("mTempFile not equal to mTargetFile"); + } +#endif + } else { + nsAutoString targetFilename; + rv = mTargetFile->GetLeafName(targetFilename); + if (NS_SUCCEEDED(rv)) { + // This will replace target. + rv = mTempFile->MoveTo(nullptr, targetFilename); + if (NS_FAILED(rv)) mTempFile->Remove(false); + } + } + } else { + mTempFile->Remove(false); + + // if writing failed, propagate the failure code to the caller. + if (NS_FAILED(mWriteResult)) rv = mWriteResult; + } + mTempFile = nullptr; + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = nsFileOutputStream::Write(buf, count, result); + if (NS_SUCCEEDED(mWriteResult)) { + if (NS_FAILED(rv)) { + mWriteResult = rv; + } else if (count != *result) { + mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + if (NS_FAILED(mWriteResult) && count > 0) { + NS_WARNING("writing to output stream failed! data may be lost"); + } + } + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsSafeFileOutputStream + +NS_IMETHODIMP +nsSafeFileOutputStream::Finish() { + (void)Flush(); + return nsAtomicFileOutputStream::Finish(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileRandomAccessStream + +nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) { + RefPtr stream = new nsFileRandomAccessStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase, + nsIRandomAccessStream, nsIFileRandomAccessStream, + nsIInputStream, nsIOutputStream) + +NS_IMETHODIMP +nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) { + nsCOMPtr inputStream(this); + + inputStream.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) { + nsCOMPtr outputStream(this); + + outputStream.forget(aOutputStream); + return NS_OK; +} + +nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; } + +nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; } + +RandomAccessStreamParams nsFileRandomAccessStream::Serialize( + nsIInterfaceRequestor* aCallbacks) { + FileRandomAccessStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen())) { + MOZ_ASSERT(mFD); + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + MOZ_ASSERT(fd, "This should never be null!"); + + params.fileDescriptor() = FileDescriptor(fd); + + Close(); + } else { + NS_WARNING( + "This file has not been opened (or could not be opened). " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptor() = FileDescriptor(); + } + + int32_t behaviorFlags = mBehaviorFlags; + + // The receiving process (or thread) is going to have an open file + // descriptor automatically so transferring this flag is meaningless. + behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN; + + params.behaviorFlags() = behaviorFlags; + + return params; +} + +bool nsFileRandomAccessStream::Deserialize( + RandomAccessStreamParams& aStreamParams) { + MOZ_ASSERT(!mFD, "Already have a file descriptor?!"); + MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!"); + + if (aStreamParams.type() != + RandomAccessStreamParams::TFileRandomAccessStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const FileRandomAccessStreamParams& params = + aStreamParams.get_FileRandomAccessStreamParams(); + + const FileDescriptor& fd = params.fileDescriptor(); + + if (fd.IsValid()) { + auto rawFD = fd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return false; + } + mFD = fileDesc; + mState = eOpened; + } else { + NS_WARNING("Received an invalid file descriptor!"); + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + mBehaviorFlags = params.behaviorFlags(); + + return true; +} + +NS_IMETHODIMP +nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + mState = eUnitialized; + + if (ioFlags == -1) ioFlags = PR_RDWR; + if (perm <= 0) perm = 0; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h new file mode 100644 index 0000000000..d402bffb24 --- /dev/null +++ b/netwerk/base/nsFileStreams.h @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFileStreams_h__ +#define nsFileStreams_h__ + +#include "mozilla/UniquePtr.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIRandomAccessStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsILineInputStream.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsReadLine.h" +#include + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} // namespace ipc +} // namespace mozilla + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileStreamBase : public nsISeekableStream, public nsIFileMetadata { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIFILEMETADATA + + nsFileStreamBase() = default; + + protected: + virtual ~nsFileStreamBase(); + + nsresult Close(); + nsresult Available(uint64_t* aResult); + nsresult Read(char* aBuf, uint32_t aCount, uint32_t* aResult); + nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval); + nsresult IsNonBlocking(bool* aNonBlocking); + nsresult Flush(); + nsresult StreamStatus(); + nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* result); + nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval); + nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval); + + PRFileDesc* mFD{nullptr}; + + /** + * Flags describing our behavior. See the IDL file for possible values. + */ + int32_t mBehaviorFlags{0}; + + enum { + // This is the default value. It will be changed by Deserialize or Init. + eUnitialized, + // The opening has been deferred. See DEFER_OPEN. + eDeferredOpen, + // The file has been opened. mFD is not null. + eOpened, + // The file has been closed. mFD is null. + eClosed, + // Something bad happen in the Open() or in Deserialize(). The actual + // error value is stored in mErrorValue. + eError + } mState{eUnitialized}; + + struct OpenParams { + nsCOMPtr localFile; + int32_t ioFlags = 0; + int32_t perm = 0; + }; + + /** + * Data we need to do an open. + */ + OpenParams mOpenParams; + + nsresult mErrorValue{NS_ERROR_FAILURE}; + + /** + * Prepares the data we need to open the file, and either does the open now + * by calling DoOpen(), or leaves it to be opened later by a call to + * DoPendingOpen(). + */ + nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm, + bool aDeferred); + + /** + * Cleans up data prepared in MaybeOpen. + */ + void CleanUpOpen(); + + /** + * Open the file. This is called either from MaybeOpen (during Init) + * or from DoPendingOpen (if DEFER_OPEN is used when initializing this + * stream). The default behavior of DoOpen is to open the file and save the + * file descriptor. + */ + virtual nsresult DoOpen(); + + /** + * Based on mState, this method does the opening, return an error, or do + * nothing. If the return value is not NS_OK, please, return it back to the + * callee. + */ + inline nsresult DoPendingOpen(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// nsFileInputStream is cloneable only on the parent process because only there +// it can open the same file multiple times. + +class nsFileInputStream : public nsFileStreamBase, + public nsIFileInputStream, + public nsILineInputStream, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEINPUTSTREAM + NS_DECL_NSILINEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + NS_IMETHOD Close() override; + NS_IMETHOD Tell(int64_t* aResult) override; + NS_IMETHOD Available(uint64_t* _retval) override; + NS_IMETHOD StreamStatus() override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, _retval); + } + NS_IMETHOD IsNonBlocking(bool* _retval) override { + return nsFileStreamBase::IsNonBlocking(_retval); + } + + // Overrided from nsFileStreamBase + NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override; + + nsFileInputStream() : mLineBuffer(nullptr) {} + + static nsresult Create(REFNSIID aIID, void** aResult); + + protected: + virtual ~nsFileInputStream() = default; + + nsresult SeekInternal(int32_t aWhence, int64_t aOffset, + bool aClearBuf = true); + + mozilla::UniquePtr> mLineBuffer; + + /** + * The file being opened. + */ + nsCOMPtr mFile; + /** + * The IO flags passed to Init() for the file open. + */ + int32_t mIOFlags{0}; + /** + * The permissions passed to Init() for the file open. + */ + int32_t mPerm{0}; + + /** + * Cached position for Tell for automatically reopening streams. + */ + int64_t mCachedPosition{0}; + + protected: + /** + * Internal, called to open a file. Parameters are the same as their + * Init() analogues. + */ + nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm); + + bool IsCloneable() const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileOutputStream : public nsFileStreamBase, public nsIFileOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEOUTPUTSTREAM + NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::) + + static nsresult Create(REFNSIID aIID, void** aResult); + nsresult InitWithFileDescriptor(const mozilla::ipc::FileDescriptor& aFd); + + protected: + virtual ~nsFileOutputStream() = default; +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing is complete. This protects against incomplete writes + * due to the process or the thread being interrupted or crashed. + */ +class nsAtomicFileOutputStream : public nsFileOutputStream, + public nsISafeOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISAFEOUTPUTSTREAM + + nsAtomicFileOutputStream() = default; + + virtual nsresult DoOpen() override; + + NS_IMETHOD Close() override; + NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override; + NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) override; + + protected: + virtual ~nsAtomicFileOutputStream() = default; + + nsCOMPtr mTargetFile; + nsCOMPtr mTempFile; + + bool mTargetFileExists{true}; + nsresult mWriteResult{NS_OK}; // Internally set in Write() +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing + flushing is complete. This protects against more + * classes of software/hardware errors than nsAtomicFileOutputStream, + * at the expense of being more costly to the disk, OS and battery. + */ +class nsSafeFileOutputStream : public nsAtomicFileOutputStream { + public: + NS_IMETHOD Finish() override; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileRandomAccessStream : public nsFileStreamBase, + public nsIFileRandomAccessStream, + public nsIInputStream, + public nsIOutputStream { + public: + static nsresult Create(REFNSIID aIID, void** aResult); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSITELLABLESTREAM(nsFileStreamBase::) + NS_FORWARD_NSISEEKABLESTREAM(nsFileStreamBase::) + NS_DECL_NSIRANDOMACCESSSTREAM + NS_DECL_NSIFILERANDOMACCESSSTREAM + NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::) + + // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods + // Close() and IsNonBlocking() + NS_IMETHOD Flush() override { return nsFileStreamBase::Flush(); } + NS_IMETHOD Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) override { + return nsFileStreamBase::Write(aBuf, aCount, _retval); + } + NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) override { + return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval); + } + NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, _retval); + } + + protected: + virtual ~nsFileRandomAccessStream() = default; +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsFileStreams_h__ diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl new file mode 100644 index 0000000000..1bf24d1d4b --- /dev/null +++ b/netwerk/base/nsIArrayBufferInputStream.idl @@ -0,0 +1,25 @@ +/* -*- 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, builtinclass, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)] +interface nsIArrayBufferInputStream : nsIInputStream +{ + /** + * SetData - assign an ArrayBuffer to the input stream. + * + * @param buffer - stream data + * @param byteOffset - stream data offset + * @param byteLen - stream data length + */ + void setData(in jsval buffer, in uint64_t byteOffset, in uint64_t byteLen); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl new file mode 100644 index 0000000000..633fe72b6e --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +// You should prefer nsIAsyncStreamCopier2 +[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)] +interface nsIAsyncStreamCopier : nsIRequest +{ + /** + * Initialize the stream copier. + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aSourceBuffered + * true if aSource implements ReadSegments. + * @param aSinkBuffered + * true if aSink implements WriteSegments. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying. + * @param aCloseSink + * true if aSink should be closed after copying. + * + * NOTE: at least one of the streams must be buffered. If you do not know + * whether your streams are buffered, you should use nsIAsyncStreamCopier2 + * instead. + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in boolean aSourceBuffered, + in boolean aSinkBuffered, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl new file mode 100644 index 0000000000..7de793f51e --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier2.idl @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)] +interface nsIAsyncStreamCopier2 : nsIRequest +{ + /** + * Initialize the stream copier. + * + * If neither the source nor the sink are buffered, buffering will + * be automatically added to the sink. + * + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying (this is generally + * the desired behavior). + * @param aCloseSink + * true if aSink should be closed after copying (this is generally + * the desired behavior). + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl new file mode 100644 index 0000000000..8c81a142f8 --- /dev/null +++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)] +interface nsIAsyncVerifyRedirectCallback : nsISupports +{ + /** + * Complement to nsIChannelEventSink asynchronous callback. The result of + * the redirect decision is passed through this callback. + * + * @param result + * Result of the redirect veto decision. If FAILED the redirect has been + * vetoed. If SUCCEEDED the redirect has been allowed by all consumers. + */ + void onRedirectVerifyCallback(in nsresult result); +}; diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl new file mode 100644 index 0000000000..a9a74891b1 --- /dev/null +++ b/netwerk/base/nsIAuthInformation.idl @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A object that hold authentication information. The caller of + * nsIAuthPrompt2::promptUsernameAndPassword or + * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this + * interface; the prompt implementation can then read the values here to prefill + * the dialog. After the user entered the authentication information, it should + * set the attributes of this object to indicate to the caller what was entered + * by the user. + */ +[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)] +interface nsIAuthInformation : nsISupports +{ + /** @name Flags */ + /* @{ */ + /** + * This dialog belongs to a network host. + */ + const uint32_t AUTH_HOST = 1; + + /** + * This dialog belongs to a proxy. + */ + const uint32_t AUTH_PROXY = 2; + + /** + * This dialog needs domain information. The user interface should show a + * domain field, prefilled with the domain attribute's value. + */ + const uint32_t NEED_DOMAIN = 4; + + /** + * This dialog only asks for password information. Authentication prompts + * SHOULD NOT show a username field. Attempts to change the username field + * will have no effect. nsIAuthPrompt2 implementations should, however, show + * its initial value to the user in some form. For example, a paragraph in + * the dialog might say "Please enter your password for user jsmith at + * server intranet". + * + * This flag is mutually exclusive with #NEED_DOMAIN. + */ + const uint32_t ONLY_PASSWORD = 8; + + /** + * We have already tried to log in for this channel + * (with auth values from a previous promptAuth call), + * but it failed, so we now ask the user to provide a new, correct login. + * + * @see also RFC 2616, Section 10.4.2 + */ + const uint32_t PREVIOUS_FAILED = 16; + + /** + * A cross-origin sub-resource requests an authentication. + * The message presented to users must reflect that. + */ + const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32; + /* @} */ + + /** + * Flags describing this dialog. A bitwise OR of the flag values + * above. + * + * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set. + * + * Auth prompts should ignore flags they don't understand; especially, they + * should not throw an exception because of an unsupported flag. + */ + readonly attribute unsigned long flags; + + /** + * The server-supplied realm of the authentication as defined in RFC 2617. + * Can be the empty string if the protocol does not support realms. + * Otherwise, this is a human-readable string like "Secret files". + */ + readonly attribute AString realm; + + /** + * The authentication scheme used for this request, if applicable. If the + * protocol for this authentication does not support schemes, this will be + * the empty string. Otherwise, this will be a string such as "basic" or + * "digest". This string will always be in lowercase. + */ + readonly attribute AUTF8String authenticationScheme; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the username entered by + * the user. + * This field can only be changed if the #ONLY_PASSWORD flag is not set. + */ + attribute AString username; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * The password should not be shown in clear. + * On return, this parameter should contain the password entered by + * the user. + */ + attribute AString password; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the domain entered by + * the user. + * This attribute is only used if flags include #NEED_DOMAIN. + */ + attribute AString domain; +}; diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl new file mode 100644 index 0000000000..87985f2a57 --- /dev/null +++ b/netwerk/base/nsIAuthModule.idl @@ -0,0 +1,146 @@ +/* vim:set ts=4 sw=4 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)] +interface nsIAuthModule : nsISupports +{ + /** + * Default behavior. + */ + const unsigned long REQ_DEFAULT = 0; + + /** + * Client and server will be authenticated. + */ + const unsigned long REQ_MUTUAL_AUTH = (1 << 0); + + /** + * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH + * flag may also need to be specified in order for this flag to take + * effect. + */ + const unsigned long REQ_DELEGATE = (1 << 1); + + /** + * The authentication is required for a proxy connection. + */ + const unsigned long REQ_PROXY_AUTH = (1 << 2); + + /** + * Flags used for telemetry. + */ + const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0; + const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1; + const unsigned long NTLM_MODULE_WIN_API_PROXY = 2; + const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3; + const unsigned long NTLM_MODULE_GENERIC_PROXY = 4; + const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5; + const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6; + const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7; + + /** Other flags may be defined in the future */ + + /** + * Called to initialize an auth module. The other methods cannot be called + * unless this method succeeds. + * + * @param aServiceName + * the service name, which may be null if not applicable (e.g., for + * NTLM, this parameter should be null). + * @param aServiceFlags + * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT + * for default behavior). + * @param aDomain + * the authentication domain, which may be null if not applicable. + * @param aUsername + * the user's login name + * @param aPassword + * the user's password + */ + void init(in ACString aServiceName, + in unsigned long aServiceFlags, + in AString aDomain, + in AString aUsername, + in AString aPassword); + + /** + * Called to get the next token in a sequence of authentication steps. + * + * @param aInToken + * A buffer containing the input token (e.g., a challenge from a + * server). This may be null. + * @param aInTokenLength + * The length of the input token. + * @param aOutToken + * If getNextToken succeeds, then aOutToken will point to a buffer + * to be sent in response to the server challenge. The length of + * this buffer is given by aOutTokenLength. The buffer at aOutToken + * must be recycled with a call to free. + * @param aOutTokenLength + * If getNextToken succeeds, then aOutTokenLength contains the + * length of the buffer (number of bytes) pointed to by aOutToken. + */ + void getNextToken([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + /** + * Once a security context has been established through calls to GetNextToken() + * it may be used to protect data exchanged between client and server. Calls + * to Wrap() are used to protect items of data to be sent to the server. + * + * @param aInToken + * A buffer containing the data to be sent to the server + * @param aInTokenLength + * The length of the input token + * @param confidential + * If set to true, Wrap() will encrypt the data, otherwise data will + * just be integrity protected (checksummed) + * @param aOutToken + * A buffer containing the resulting data to be sent to the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication + * mechanism does not support security layers. + */ + void wrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + in boolean confidential, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + + /** + * Unwrap() is used to unpack, decrypt, and verify the checksums on data + * returned by a server when security layers are in use. + * + * @param aInToken + * A buffer containing the data received from the server + * @param aInTokenLength + * The length of the input token + * @param aOutToken + * A buffer containing the plaintext data from the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying + * authentication mechanism does not support security layers. + */ + void unwrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + +%{C++ + /** + * Create a new instance of an auth module. + * + * @param aType + * The type of the auth module to be constructed. + */ + static already_AddRefed CreateInstance(const char* aType); +%} +}; diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl new file mode 100644 index 0000000000..fbbac6ee9f --- /dev/null +++ b/netwerk/base/nsIAuthPrompt.idl @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrompt; + +[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)] +interface nsIAuthPrompt : nsISupports +{ + const uint32_t SAVE_PASSWORD_NEVER = 0; + const uint32_t SAVE_PASSWORD_FOR_SESSION = 1; + const uint32_t SAVE_PASSWORD_PERMANENTLY = 2; + + /** + * Puts up a text input dialog with OK and Cancel buttons. + * Note: prompt uses separate args for the "in" and "out" values of the + * input field, whereas the other functions use a single inout arg. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param defaultText The default text to display in the text input box. + * @param result The value entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean prompt(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + in wstring defaultText, + out wstring result); + + /** + * Puts up a username/password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param user The username entered in the dialog. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring user, + inout wstring pwd); + + /** + * Puts up an async username/password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param user The username entered in the dialog. + * @param pwd The password entered by the user if OK was + * selected. + * @return promise resolving to true for OK, false for Cancel + */ + Promise asyncPromptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring user, + inout wstring pwd); + + /** + * Puts up a password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test. If a username is + * specified (http://user@site.com) it will be used + * when matching existing logins or saving new ones. + * If no username is specified, only password-only + * logins will be matched or saved. + * Note: if a username is specified, the username + * should be escaped. + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring pwd); + + /** + * Puts up an async password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test. If a username is + * specified (http://user@site.com) it will be used + * when matching existing logins or saving new ones. + * If no username is specified, only password-only + * logins will be matched or saved. + * Note: if a username is specified, the username + * should be escaped. + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param pwd The password entered by the user if OK was + * selected. + * @return promise resolving to true for OK, false for Cancel + */ + Promise asyncPromptPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring pwd); +}; diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl new file mode 100644 index 0000000000..d94d50dfcc --- /dev/null +++ b/netwerk/base/nsIAuthPrompt2.idl @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPromptCallback; +interface nsIChannel; +interface nsICancelable; +interface nsIAuthInformation; + +/** + * An interface allowing to prompt for a username and password. This interface + * is usually acquired using getInterface on notification callbacks or similar. + * It can be used to prompt users for authentication information, either + * synchronously or asynchronously. + */ +[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)] +interface nsIAuthPrompt2 : nsISupports +{ + /** @name Security Levels */ + /* @{ */ + /** + * The password will be sent unencrypted. No security provided. + */ + const uint32_t LEVEL_NONE = 0; + /** + * Password will be sent encrypted, but the connection is otherwise + * insecure. + */ + const uint32_t LEVEL_PW_ENCRYPTED = 1; + /** + * The connection, both for password and data, is secure. + */ + const uint32_t LEVEL_SECURE = 2; + /* @} */ + + /** + * Requests a username and a password. Implementations will commonly show a + * dialog with a username and password field, depending on flags also a + * domain field. + * + * @param aChannel + * The channel that requires authentication. + * @param level + * One of the level constants from above. See there for descriptions + * of the levels. + * @param authInfo + * Authentication information object. The implementation should fill in + * this object with the information entered by the user before + * returning. + * + * @retval true + * Authentication can proceed using the values in the authInfo + * object. + * @retval false + * Authentication should be cancelled, usually because the user did + * not provide username/password. + * + * @note Exceptions thrown from this function will be treated like a + * return value of false. + * @deprecated use asyncPromptAuth + */ + boolean promptAuth(in nsIChannel aChannel, + in uint32_t level, + in nsIAuthInformation authInfo); + + /** + * Asynchronously prompt the user for a username and password. + * This has largely the same semantics as promptUsernameAndPassword(), + * but must return immediately after calling and return the entered + * data in a callback. + * + * If the user closes the dialog using a cancel button or similar, + * the callback's nsIAuthPromptCallback::onAuthCancelled method must be + * called. + * Calling nsICancelable::cancel on the returned object SHOULD close the + * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided + * callback. + * + * This implementation may: + * + * 1) Coalesce identical prompts. This means prompts that are guaranteed to + * want the same auth information from the user. A single prompt will be + * shown; then the callbacks for all the coalesced prompts will be notified + * with the resulting auth information. + * 2) Serialize prompts that are all in the same "context" (this might mean + * application-wide, for a given window, or something else depending on + * the user interface) so that the user is not deluged with prompts. + * + * @throw + * This method may throw any exception when the prompt fails to queue e.g + * because of out-of-memory error. It must not throw when the prompt + * could already be potentially shown to the user. In that case information + * about the failure has to come through the callback. This way we + * prevent multiple dialogs shown to the user because consumer may fall + * back to synchronous prompt on synchronous failure of this method. + */ + nsICancelable asyncPromptAuth(in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo); +}; diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl new file mode 100644 index 0000000000..e763d47146 --- /dev/null +++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPrompt; +interface nsIAuthPrompt2; + +/** + * An interface for wrapping nsIAuthPrompt interfaces to make + * them usable via an nsIAuthPrompt2 interface. + */ +[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)] +interface nsIAuthPromptAdapterFactory : nsISupports +{ + /** + * Wrap an object implementing nsIAuthPrompt so that it's usable via + * nsIAuthPrompt2. + */ + nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt); +}; diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl new file mode 100644 index 0000000000..27f89f04c0 --- /dev/null +++ b/netwerk/base/nsIAuthPromptCallback.idl @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthInformation; + +/** + * Interface for callback methods for the asynchronous nsIAuthPrompt2 method. + * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync + * returns successfully. They MUST NOT call any method on this interface before + * promptPasswordAsync returns. + */ +[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)] +interface nsIAuthPromptCallback : nsISupports +{ + /** + * Authentication information is available. + * + * @param aContext + * The context as passed to promptPasswordAsync + * @param aAuthInfo + * Authentication information. Must be the same object that was passed + * to promptPasswordAsync. + * + * @note Any exceptions thrown from this method should be ignored. + */ + void onAuthAvailable(in nsISupports aContext, + in nsIAuthInformation aAuthInfo); + + /** + * Notification that the prompt was cancelled. + * + * @param aContext + * The context that was passed to promptPasswordAsync. + * @param userCancel + * If false, this prompt was cancelled by calling the + * the cancel method on the nsICancelable; otherwise, + * it was cancelled by the user. + */ + void onAuthCancelled(in nsISupports aContext, in boolean userCancel); +}; diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl new file mode 100644 index 0000000000..e8ff122ec2 --- /dev/null +++ b/netwerk/base/nsIAuthPromptProvider.idl @@ -0,0 +1,34 @@ +/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)] +interface nsIAuthPromptProvider : nsISupports +{ + /** + * Normal (non-proxy) prompt request. + */ + const uint32_t PROMPT_NORMAL = 0; + + /** + * Proxy auth request. + */ + const uint32_t PROMPT_PROXY = 1; + + /** + * Request a prompt interface for the given prompt reason; + * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or + * available for the given reason. + * + * @param aPromptReason The reason for the auth prompt; + * one of #PROMPT_NORMAL or #PROMPT_PROXY + * @param iid The desired interface, e.g. + * NS_GET_IID(nsIAuthPrompt2). + * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE + */ + void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); +}; diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl new file mode 100644 index 0000000000..0b26852c28 --- /dev/null +++ b/netwerk/base/nsIBackgroundFileSaver.idl @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIArray; +interface nsIBackgroundFileSaverObserver; +interface nsIFile; + +/** + * Allows saving data to a file, while handling all the input/output on a + * background thread, including the initial file name assignment and any + * subsequent renaming of the target file. + * + * This interface is designed for file downloads. Generally, they start in the + * temporary directory, while the user is selecting the final name. Then, they + * are moved to the chosen target directory with a ".part" extension appended to + * the file name. Finally, they are renamed when the download is completed. + * + * Components implementing both nsIBackgroundFileSaver and nsIStreamListener + * allow data to be fed using an implementation of OnDataAvailable that never + * blocks the calling thread. They suspend the request that drives the stream + * listener in case too much data is being fed, and resume it when the data has + * been written. Calling OnStopRequest does not necessarily close the target + * file, and the Finish method must be called to complete the operation. + * + * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream + * allow data to be fed directly to the non-blocking output stream, that however + * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed. + * Closing the output stream does not necessarily close the target file, and the + * Finish method must be called to complete the operation. + * + * @remarks Implementations may require the consumer to always call Finish. If + * the object reference is released without calling Finish, a memory + * leak may occur, and the target file might be kept locked. All + * public methods of the interface may only be called from the main + * thread. + */ +[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)] +interface nsIBackgroundFileSaver : nsISupports +{ + /** + * This observer receives notifications when the target file name changes and + * when the operation completes, successfully or not. + * + * @remarks A strong reference to the observer is held. Notification events + * are dispatched to the thread that created the object that + * implements nsIBackgroundFileSaver. + */ + attribute nsIBackgroundFileSaverObserver observer; + + /** + * An Array of Array of Array of bytes, representing a chain of + * X.509 certificates, the last of which signed the downloaded file. + * Each list may belong to a different signer and contain certificates + * all the way up to the root. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case this is called before the onSaveComplete method has been + * called to notify success, or enableSignatureInfo has not been + * called. + */ + readonly attribute Array > > signatureInfo; + + /** + * The SHA-256 hash, in raw bytes, associated with the data that was saved. + * + * In case the enableAppend method has been called, the hash computation + * includes the contents of the existing file, if any. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case the enableSha256 method has not been called, or before the + * onSaveComplete method has been called to notify success. + */ + readonly attribute ACString sha256Hash; + + /** + * Instructs the component to compute the signatureInfo of the target file, + * and make it available in the signatureInfo property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSignatureInfo(); + + /** + * Instructs the component to compute the SHA-256 hash of the target file, and + * make it available in the sha256Hash property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSha256(); + + /** + * Instructs the component to append data to the initial target file, that + * will be specified by the first call to the setTarget method, instead of + * overwriting the file. + * + * If the initial target file does not exist, this method has no effect. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableAppend(); + + /** + * Sets the name of the output file to be written. The target can be changed + * after data has already been fed, in which case the existing file will be + * moved to the new destination. + * + * In case the specified file already exists, and this method is called for + * the first time, the file may be either overwritten or appended to, based on + * whether the enableAppend method was called. Subsequent calls always + * overwrite the specified target file with the previously saved data. + * + * No file will be written until this function is called at least once. It's + * recommended not to feed any data until the output file is set. + * + * If an input/output error occurs with the specified file, the save operation + * fails. Failure is notified asynchronously through the observer. + * + * @param aTarget + * New output file to be written. + * @param aKeepPartial + * Indicates whether aFile should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. This is + * generally set for downloads that use temporary ".part" files. + */ + void setTarget(in nsIFile aTarget, in bool aKeepPartial); + + /** + * Terminates access to the output file, then notifies the observer with the + * specified status code. A failure code will force the operation to be + * canceled, in which case the output file will be deleted if requested. + * + * This forces the involved streams to be closed, thus no more data should be + * fed to the component after this method has been called. + * + * This is the last method that should be called on this object, and the + * target file name cannot be changed anymore after this method has been + * called. Conversely, before calling this method, the file can still be + * renamed even if all the data has been fed. + * + * @param aStatus + * Result code that determines whether the operation should succeed or + * be canceled, and is notified to the observer. If the operation + * fails meanwhile for other reasons, or the observer has been already + * notified of completion, this status code is ignored. + */ + void finish(in nsresult aStatus); +}; + +[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)] +interface nsIBackgroundFileSaverObserver : nsISupports +{ + /** + * Called when the name of the output file has been determined. This function + * may be called more than once if the target file is renamed while saving. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aTarget + * Name of the file that is being written. + */ + void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget); + + /** + * Called when the operation completed, and the target file has been closed. + * If the operation succeeded, the target file is ready to be used, otherwise + * it might have been already deleted. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aStatus + * Result code that determines whether the operation succeeded or + * failed, as well as the failure reason. + */ + void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus); +}; diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl new file mode 100644 index 0000000000..46f0112900 --- /dev/null +++ b/netwerk/base/nsIBufferedStreams.idl @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" + +/** + * An input stream that reads ahead and keeps a buffer coming from another input + * stream so that fewer accesses to the underlying stream are necessary. + */ +[scriptable, builtinclass, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedInputStream : nsIInputStream +{ + /** + * @param fillFromStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIInputStream fillFromStream, + in unsigned long bufferSize); + + /** + * Get the wrapped data stream + */ + readonly attribute nsIInputStream data; +}; + +/** + * An output stream that stores up data to write out to another output stream + * and does the entire write only when the buffer is full, so that fewer writes + * to the underlying output stream are necessary. + */ +[scriptable, builtinclass, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedOutputStream : nsIOutputStream +{ + /** + * @param sinkToStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIOutputStream sinkToStream, + in unsigned long bufferSize); + + /** + * Get the wrapped data stream + */ + readonly attribute nsIOutputStream data; +}; diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl new file mode 100644 index 0000000000..4a10126d69 --- /dev/null +++ b/netwerk/base/nsIByteRangeRequest.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)] +interface nsIByteRangeRequest : nsISupports +{ + /** + * Returns true IFF this request is a byte range request, otherwise it + * returns false (This is effectively the same as checking to see if + * |startRequest| is zero and |endRange| is the content length.) + */ + readonly attribute boolean isByteRangeRequest; + + /** + * Absolute start position in remote file for this request. + */ + readonly attribute long long startRange; + + /** + * Absolute end postion in remote file for this request + */ + readonly attribute long long endRange; +}; diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl new file mode 100644 index 0000000000..b7bb44093c --- /dev/null +++ b/netwerk/base/nsICacheInfoChannel.idl @@ -0,0 +1,207 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAsyncOutputStream; +interface nsIInputStream; + +%{C++ +#include "nsTArray.h" +namespace mozilla { +namespace net { +class PreferredAlternativeDataTypeParams; +} +} // namespace mozilla +%} + +[ref] native ConstPreferredAlternativeDataTypeArray(const nsTArray); + +[scriptable, uuid(1fb8ccf2-5fa5-45ec-bc57-8c8022a5d0d3)] +interface nsIInputStreamReceiver : nsISupports +{ + void onInputStreamReady(in nsIInputStream aStream); +}; + +[scriptable, builtinclass, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)] +interface nsICacheInfoChannel : nsISupports +{ + /** + * Get the number of times the cache entry has been opened. This attribute is + * equivalent to nsICachingChannel.cacheToken.fetchCount. + * + * @throws NS_ERROR_NOT_AVAILABLE if the cache entry or the alternate data + * cache entry cannot be read. + */ + readonly attribute uint32_t cacheTokenFetchCount; + + /** + * Get expiration time from cache token. This attribute is equivalent to + * nsICachingChannel.cacheToken.expirationTime. + */ + readonly attribute uint32_t cacheTokenExpirationTime; + + /** + * TRUE if this channel's data is being loaded from the cache. This value + * is undefined before the channel fires its OnStartRequest notification + * and after the channel fires its OnStopRequest notification. + */ + boolean isFromCache(); + + /** + * Returns true if the channel raced the cache and network requests. + * In order to determine if the response is coming from the cache or the + * network, the consumer can check isFromCache(). + * The method can only be called after the channel fires its OnStartRequest + * notification. + */ + boolean isRacing(); + + /** + * The unique ID of the corresponding nsICacheEntry from which the response is + * retrieved. By comparing the returned value, we can judge whether the data + * of two distinct nsICacheInfoChannels is from the same nsICacheEntry. This + * scenario could be useful when verifying whether the alternative data from + * one nsICacheInfochannel matches the main data from another one. + * + * Note: NS_ERROR_NOT_AVAILABLE is thrown when a nsICacheInfoChannel has no + * valid corresponding nsICacheEntry. + */ + uint64_t getCacheEntryId(); + + /** + * Set/get the cache key. This integer uniquely identifies the data in + * the cache for this channel. + * + * A cache key retrieved from a particular instance of nsICacheInfoChannel + * could be set on another instance of nsICacheInfoChannel provided the + * underlying implementations are compatible and provided the new + * channel instance was created with the same URI. The implementation of + * nsICacheInfoChannel would be expected to use the cache entry identified + * by the cache token. Depending on the value of nsIRequest::loadFlags, + * the cache entry may be validated, overwritten, or simply read. + * + * The cache key may be 0 indicating that the URI of the channel is + * sufficient to locate the same cache entry. Setting a 0 cache key + * is likewise valid. + */ + attribute unsigned long cacheKey; + + /** + * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set, + * but without affecting the loads for the entire loadGroup in case of this + * channel being the default load group's channel. + */ + attribute boolean allowStaleCacheContent; + + /** + * Tells the priority for LOAD_CACHE is raised over LOAD_BYPASS_CACHE or + * LOAD_BYPASS_LOCAL_CACHE in case those flags are set at the same time. + */ + attribute boolean preferCacheLoadOverBypass; + + cenum PreferredAlternativeDataDeliveryType : 8 { + /** + * Do not deliver alternative data stream. + */ + NONE = 0, + + /** + * Deliver alternative data stream upon additional request. + */ + ASYNC = 1, + + /** + * Deliver alternative data stream during IPC parent/child serialization. + */ + SERIALIZE = 2, + }; + + /** + * Tells the channel to be force validated during soft reload. + */ + attribute boolean forceValidateCacheContent; + + /** + * Calling this method instructs the channel to serve the alternative data + * if that was previously saved in the cache, otherwise it will serve the + * real data. + * @param type + * a string identifying the alt-data format + * @param contentType + * the contentType for which the preference applies. + * an empty contentType means the preference applies for ANY contentType + * @param deliverAltData + * if false, also if alt-data is available, the channel will deliver + * the original data. + * + * The method may be called several times, with different type and contentType. + * + * Must be called before AsyncOpen. + */ + void preferAlternativeDataType(in ACString type, in ACString contentType, + in nsICacheInfoChannel_PreferredAlternativeDataDeliveryType deliverAltData); + + /** + * Get the preferred alternative data type set by preferAlternativeDataType(). + * The returned types stand for the desired data type instead of the type of the + * information retrieved from the network stack. + */ + [noscript, notxpcom, nostdcall] + ConstPreferredAlternativeDataTypeArray preferredAlternativeDataTypes(); + + /** + * Holds the type of the alternative data representation that the channel + * is returning. + * Is empty string if no alternative data representation was requested, or + * if the requested representation wasn't found in the cache. + * Can only be called during or after OnStartRequest. + */ + readonly attribute ACString alternativeDataType; + + /** + * If preferAlternativeDataType() has been called passing deliverAltData + * equal to false, this attribute will expose the alt-data inputStream if + * avaiable. + */ + readonly attribute nsIInputStream alternativeDataInputStream; + + /** + * Sometimes when the channel is delivering alt-data, we may want to somehow + * access the original content too. This method asynchronously opens the + * input stream and delivers it to the receiver. + */ + void getOriginalInputStream(in nsIInputStreamReceiver aReceiver); + + /** + * Opens and returns an output stream that a consumer may use to save an + * alternate representation of the data. + * Must be called after the OnStopRequest that delivered the real data. + * The consumer may choose to replace the saved alt representation. + * Opening the output stream will fail if there are any open input streams + * reading the already saved alt representation. After successfully opening + * an output stream, if there is an error before the entire alt data can be + * written successfully, the client must signal failure by passing an error + * code to CloseWithStatus(). + * + * @param type + * type of the alternative data representation + * @param predictedSize + * Predicted size of the data that will be written. It's used to decide + * whether the resulting entry would exceed size limit, in which case + * an error is thrown. If the size isn't known in advance, -1 should be + * passed. + */ + nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize); +}; + +%{ C++ +namespace mozilla { +namespace net { + +using PreferredAlternativeDataDeliveryTypeIPC = nsICacheInfoChannel::PreferredAlternativeDataDeliveryType; + +} +} // namespace mozilla +%} diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl new file mode 100644 index 0000000000..3623423ff7 --- /dev/null +++ b/netwerk/base/nsICachingChannel.idl @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICacheInfoChannel.idl" + +interface nsIFile; + +/** + * A channel may optionally implement this interface to allow clients + * to affect its behavior with respect to how it uses the cache service. + * + * This interface provides: + * 1) Support for "stream as file" semantics (for JAR and plugins). + * 2) Support for "pinning" cached data in the cache (for printing and save-as). + * 3) Support for uniquely identifying cached data in cases when the URL + * is insufficient (e.g., HTTP form submission). + */ +[scriptable, builtinclass, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)] +interface nsICachingChannel : nsICacheInfoChannel +{ + /** + * Set/get the cache token... uniquely identifies the data in the cache. + * Holding a reference to this token prevents the cached data from being + * removed. + * + * A cache token retrieved from a particular instance of nsICachingChannel + * could be set on another instance of nsICachingChannel provided the + * underlying implementations are compatible. The implementation of + * nsICachingChannel would be expected to only read from the cache entry + * identified by the cache token and not try to validate it. + * + * The cache token can be QI'd to a nsICacheEntryInfo if more detail + * about the cache entry is needed (e.g., expiration time). + */ + attribute nsISupports cacheToken; + + /** + * Instructs the channel to only store the metadata of the entry, and not + * the content. When reading an existing entry, this automatically sets + * LOAD_ONLY_IF_MODIFIED flag. + * Must be called before asyncOpen(). + */ + attribute boolean cacheOnlyMetadata; + + /** + * Tells the channel to use the pinning storage. + */ + attribute boolean pin; + + /** + * Overrides cache validation for a time specified in seconds. + * + * @param aSecondsToTheFuture + * + */ + void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture); + + /************************************************************************** + * Caching channel specific load flags: + */ + + /** + * This load flag inhibits fetching from the net. An error of + * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's + * onStopRequest if network IO is necessary to complete the request. + * + * This flag can be used to find out whether fetching this URL would + * cause validation of the cache entry via the network. + * + * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all + * loads to fail. + */ + const unsigned long LOAD_NO_NETWORK_IO = 1 << 26; + + /** + * This load flag causes the local cache to be skipped when fetching a + * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load + * (i.e., it does not affect proxy caches). + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28; + + /** + * This load flag causes the local cache to be skipped if the request + * would otherwise block waiting to access the cache. + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29; + + /** + * This load flag inhibits fetching from the net if the data in the cache + * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent + * to the listener's onStopRequest in this case. This flag is set + * automatically when the application is offline. + */ + const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30; + + /** + * This load flag controls what happens when a document would be loaded + * from the cache to satisfy a call to AsyncOpen. If this attribute is + * set to TRUE, then the document will not be loaded from the cache. A + * stream listener can check nsICachingChannel::isFromCache to determine + * if the AsyncOpen will actually result in data being streamed. + * + * If this flag has been set, and the request can be satisfied via the + * cache, then the OnDataAvailable events will be skipped. The listener + * will only see OnStartRequest followed by OnStopRequest. + */ + const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31; +}; diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl new file mode 100644 index 0000000000..c558dc6e27 --- /dev/null +++ b/netwerk/base/nsICancelable.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface provides a means to cancel an operation that is in progress. + */ +[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)] +interface nsICancelable : nsISupports +{ + /** + * Call this method to request that this object abort whatever operation it + * may be performing. + * + * @param aReason + * Pass a failure code to indicate the reason why this operation is + * being canceled. It is an error to pass a success code. + */ + void cancel(in nsresult aReason); +}; diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl new file mode 100644 index 0000000000..e4867765d7 --- /dev/null +++ b/netwerk/base/nsICaptivePortalService.idl @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)] +interface nsICaptivePortalServiceCallback : nsISupports +{ + /** + * Invoke callbacks after captive portal detection finished. + */ + void complete(in bool success, in nsresult error); +}; + +/** + * Service used for captive portal detection. + * The service is only active in the main process. It is also available in the + * content process, but only to mirror the captive portal state from the main + * process. + */ +[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)] +interface nsICaptivePortalService : nsISupports +{ + const long UNKNOWN = 0; + const long NOT_CAPTIVE = 1; + const long UNLOCKED_PORTAL = 2; + const long LOCKED_PORTAL = 3; + + /** + * Called from XPCOM to trigger a captive portal recheck. + * A network request will only be performed if no other checks are currently + * ongoing. + * Will not do anything if called in the content process. + */ + void recheckCaptivePortal(); + + /** + * Returns the state of the captive portal. + */ + readonly attribute long state; + +%{C++ + int32_t State() { + int32_t result = nsICaptivePortalService::UNKNOWN; + GetState(&result); + return result; + } +%} + + /** + * Returns the time difference between NOW and the last time a request was + * completed in milliseconds. + */ + readonly attribute unsigned long long lastChecked; +}; + +%{C++ +/** + * This observer notification will be emitted when the captive portal state + * changes. After receiving it, the ContentParent will send an IPC message + * to the ContentChild, which will set the state in the captive portal service + * in the child. + */ +#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state" + +/** + * This notification will be emitted when the captive portal service has + * determined that we can connect to the internet. + * The service will pass either "captive" if there is an unlocked captive portal + * present, or "clear" if no captive portal was detected. + * Note: this notification only gets sent in the parent process. + */ +#define NS_CAPTIVE_PORTAL_CONNECTIVITY "network:captive-portal-connectivity" + +/** + * Similar to NS_CAPTIVE_PORTAL_CONNECTIVITY but only gets dispatched when + * the connectivity changes from UNKNOWN to NOT_CAPTIVE or from LOCKED_PORTAL + * to UNLOCKED_PORTAL. + */ +#define NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED "network:captive-portal-connectivity-changed" + +%} diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl new file mode 100644 index 0000000000..2269c4faa8 --- /dev/null +++ b/netwerk/base/nsIChannel.idl @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" +#include "nsILoadInfo.idl" + +interface nsIInputStream; +interface nsIInterfaceRequestor; +interface nsIStreamListener; +interface nsITransportSecurityInfo; +interface nsIURI; + +%{C++ +#include "nsCOMPtr.h" +%} + +/** + * The nsIChannel interface allows clients to construct "GET" requests for + * specific protocols, and manage them in a uniform way. Once a channel is + * created (via nsIIOService::newChannel), parameters for that request may + * be set by using the channel attributes, or by QI'ing to a subclass of + * nsIChannel for protocol-specific parameters. Then, the URI can be fetched + * by calling nsIChannel::open or nsIChannel::asyncOpen. + * + * After a request has been completed, the channel is still valid for accessing + * protocol-specific results. For example, QI'ing to nsIHttpChannel allows + * response headers to be retrieved for the corresponding http transaction. + * + * This interface must be used only from the XPCOM main thread. + */ +[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)] +interface nsIChannel : nsIRequest +{ + /** + * The original URI used to construct the channel. This is used in + * the case of a redirect or URI "resolution" (e.g. resolving a + * resource: URI to a file: URI) so that the original pre-redirect + * URI can still be obtained. This is never null. Attempts to + * set it to null must throw. + * + * NOTE: this is distinctly different from the http Referer (referring URI), + * which is typically the page that contained the original URI (accessible + * from nsIHttpChannel). + * + * NOTE: originalURI isn't yet set on the new channel when + * asyncOnChannelRedirect is called. + */ + attribute nsIURI originalURI; + + /** + * The URI corresponding to the channel. Its value is immutable. + */ + readonly attribute nsIURI URI; + + /** + * The owner, corresponding to the entity that is responsible for this + * channel. Used by the security manager to grant or deny privileges to + * mobile code loaded from this channel. + * + * NOTE: this is a strong reference to the owner, so if the owner is also + * holding a strong reference to the channel, care must be taken to + * explicitly drop its reference to the channel. + */ + attribute nsISupports owner; + + /** + * The notification callbacks for the channel. This is set by clients, who + * wish to provide a means to receive progress, status and protocol-specific + * notifications. If this value is NULL, the channel implementation may use + * the notification callbacks from its load group. The channel may also + * query the notification callbacks from its load group if its notification + * callbacks do not supply the requested interface. + * + * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt, + * and nsIAuthPrompt/nsIAuthPrompt2. + * + * When the channel is done, it must not continue holding references to + * this object. + * + * NOTE: A channel implementation should take care when "caching" an + * interface pointer queried from its notification callbacks. If the + * notification callbacks are changed, then a cached interface pointer may + * become invalid and may therefore need to be re-queried. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Transport-level security information (if any) corresponding to the + * channel. + * + * NOTE: In some circumstances TLS information is propagated onto + * non-nsIHttpChannel objects to indicate that their contents were likely + * delivered over TLS all the same. + * + * FIXME(bz, bug 1528449) is that still true now that + * document.open() doesn't do this? + */ + readonly attribute nsITransportSecurityInfo securityInfo; + + /** + * The MIME type of the channel's content if available. + * + * NOTE: the content type can often be wrongly specified (e.g., wrong file + * extension, wrong MIME type, wrong document type stored on a server, etc.), + * and the caller most likely wants to verify with the actual data. + * + * Setting contentType before the channel has been opened provides a hint + * to the channel as to what the MIME type is. The channel may ignore this + * hint in deciding on the actual MIME type that it will report. + * + * Setting contentType after onStartRequest has been fired or after open() + * is called will override the type determined by the channel. + * + * Setting contentType between the time that asyncOpen() is called and the + * time when onStartRequest is fired has undefined behavior at this time. + * + * The value of the contentType attribute is a lowercase string. A value + * assigned to this attribute will be parsed and normalized as follows: + * 1- any parameters (delimited with a ';') will be stripped. + * 2- if a charset parameter is given, then its value will replace the + * the contentCharset attribute of the channel. + * 3- the stripped contentType will be lowercased. + * Any implementation of nsIChannel must follow these rules. + */ + attribute ACString contentType; + + /** + * The character set of the channel's content if available and if applicable. + * This attribute only applies to textual data. + * + * The value of the contentCharset attribute is a mixedcase string. + */ + attribute ACString contentCharset; + + /** + * The length of the data associated with the channel if available. A value + * of -1 indicates that the content length is unknown. Note that this is a + * 64-bit value and obsoletes the "content-length" property used on some + * channels. + */ + attribute int64_t contentLength; + + /** + * Synchronously open the channel. + * + * @return blocking input stream to the channel's data. + * + * NOTE: nsIChannel implementations are not required to implement this + * method. Moreover, since this method may block the calling thread, it + * should not be called on a thread that processes UI events. Like any + * other nsIChannel method it must not be called on any thread other + * than the XPCOM main thread. + * + * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel + * is reopened. + */ + nsIInputStream open(); + + /** + * Asynchronously open this channel. Data is fed to the specified stream + * listener as it becomes available. The stream listener's methods are + * called on the thread that calls asyncOpen and are not called until + * after asyncOpen returns. If asyncOpen returns successfully, the + * channel promises to call at least onStartRequest and onStopRequest. + * + * If the nsIRequest object passed to the stream listener's methods is not + * this channel, an appropriate onChannelRedirect notification needs to be + * sent to the notification callbacks before onStartRequest is called. + * Once onStartRequest is called, all following method calls on aListener + * will get the request that was passed to onStartRequest. + * + * If the channel's and loadgroup's notification callbacks do not provide + * an nsIChannelEventSink when onChannelRedirect would be called, that's + * equivalent to having called onChannelRedirect. + * + * If asyncOpen returns successfully, the channel is responsible for + * keeping itself alive until it has called onStopRequest on aListener or + * called onChannelRedirect. + * + * Implementations are allowed to synchronously add themselves to the + * associated load group (if any). + * + * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the + * channel is reopened. + * NOTE: Implementations should throw an error if the channel has been + * cancelled prior asyncOpen being called. + * + * @param aListener the nsIStreamListener implementation + * @see nsIChannelEventSink for onChannelRedirect + */ + void asyncOpen(in nsIStreamListener aListener); + + /** + * True if the channel has been canceled. + */ + [must_use] readonly attribute boolean canceled; + + /************************************************************************** + * Channel specific load flags: + * + * Bits 16-31 are reserved for future use by this interface or one of its + * derivatives (e.g., see nsICachingChannel). + */ + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to a document URI. + * While setting this flag is sufficient to mark a channel as a document + * load, _checking_ whether the channel is a document load requires the use + * of the new channel.isDocument + */ + const unsigned long LOAD_DOCUMENT_URI = 1 << 16; + + /** + * If the end consumer for this load has been retargeted after discovering + * its content, this flag will be set: + */ + const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17; + + /** + * This flag is set to indicate that this channel is replacing another + * channel. This means that: + * + * 1) the stream listener this channel will be notifying was initially + * passed to the asyncOpen method of some other channel + * + * and + * + * 2) this channel's URI is a better identifier of the resource being + * accessed than this channel's originalURI. + * + * This flag can be set, for example, for redirects or for cases when a + * single channel has multiple parts to it (and thus can follow + * onStopRequest with another onStartRequest/onStopRequest pair, each pair + * for a different request). + */ + const unsigned long LOAD_REPLACE = 1 << 18; + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to an initial document URI load (e.g., link click). + */ + const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19; + + /** + * Set (e.g., by the URILoader) to indicate whether or not the end consumer + * for this load has been determined. + */ + const unsigned long LOAD_TARGETED = 1 << 20; + + /** + * If this flag is set, the channel should call the content sniffers as + * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY. + * + * Note: Channels may ignore this flag; however, new channel implementations + * should only do so with good reason. + */ + const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21; + + /** + * This flag tells the channel to bypass URL classifier service check + * when opening the channel. + */ + const unsigned long LOAD_BYPASS_URL_CLASSIFIER = 1 << 22; + + /** + * If this flag is set, the media-type content sniffer will be allowed + * to override any server-set content-type. Otherwise it will only + * be allowed to override "no content type" and application/octet-stream. + */ + const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23; + + /** + * Set to let explicitely provided credentials be used over credentials + * we have cached previously. In some situations like form login using HTTP + * auth via XMLHttpRequest we need to let consumers override the cached + * credentials explicitely. For form login 403 response instead of 401 is + * usually used to prevent an auth dialog. But any code other then 401/7 + * will leave original credentials in the cache and there is then no way + * to override them for the same user name. + */ + const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24; + + /** + * Set to force bypass of any service worker interception of the channel. + */ + const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25; + + // nsICachingChannel load flags begin at bit 26. + + /** + * Access to the type implied or stated by the Content-Disposition header + * if available and if applicable. This allows determining inline versus + * attachment. + * + * Setting contentDisposition provides a hint to the channel about the + * disposition. If the hint is DISPOSITION_ATTACHMENT and a normal + * Content-Disposition header is present, the hinted value will always be + * used. If the hint is DISPOSITION_FORCE_INLINE then the disposition is + * inline and the header is not used. The value from Content-Disposition + * header is only used when the hinted value is not DISPOSITION_INLINE or + * DISPOSITION_FORCE_INLINE. + * If the header is missing the hinted value will be used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty, and return + * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present. + */ + attribute unsigned long contentDisposition; + const unsigned long DISPOSITION_INLINE = 0; + const unsigned long DISPOSITION_ATTACHMENT = 1; + const unsigned long DISPOSITION_FORCE_INLINE = 2; + + /** + * Access to the filename portion of the Content-Disposition header if + * available and if applicable. This allows getting the preferred filename + * without having to parse it out yourself. + * + * Setting contentDispositionFilename provides a hint to the channel about + * the disposition. If a normal Content-Disposition header is present its + * value will always be used. If it is missing the hinted value will be + * used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't + * exist for this type of channel, if the header is empty, if the header + * doesn't contain a filename portion, or the value of the filename + * attribute is empty/missing. + */ + attribute AString contentDispositionFilename; + + /** + * Access to the raw Content-Disposition header if available and applicable. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty. + * + * @deprecated Use contentDisposition/contentDispositionFilename instead. + */ + readonly attribute ACString contentDispositionHeader; + + /** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ + attribute nsILoadInfo loadInfo; + + /** + * Returns true if the channel is used to create a document. + * It returns true if the loadFlags have LOAD_DOCUMENT_URI set, or if + * LOAD_HTML_OBJECT_DATA is set and the channel has the appropriate + * MIME type. + * Note: May have the wrong value if called before OnStartRequest as we + * don't know the MIME type yet. + */ + readonly attribute bool isDocument; + +%{ C++ + inline bool IsDocument() + { + bool isDocument = false; + if (NS_SUCCEEDED(GetIsDocument(&isDocument)) && isDocument) { + return true; + } + return false; + } + + inline already_AddRefed LoadInfo() + { + nsCOMPtr result; + mozilla::DebugOnly rv = GetLoadInfo(getter_AddRefs(result)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && result); + return result.forget(); + } +%} + +}; + +[builtinclass, scriptable, uuid(1ebbff64-d742-4f4a-aad5-4df2d1eb937a)] +interface nsIIdentChannel : nsIChannel +{ + /** + * Unique ID of the channel, shared between parent and child. Needed if + * the channel activity needs to be monitored across process boundaries, + * like in devtools net monitor. See bug 1274556. + */ + [must_use] attribute uint64_t channelId; + +%{ C++ + inline uint64_t ChannelId() + { + uint64_t value = 0; + if (NS_SUCCEEDED(GetChannelId(&value))) { + return value; + } + return 0; + } +%} +}; diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl new file mode 100644 index 0000000000..1f77cfb466 --- /dev/null +++ b/netwerk/base/nsIChannelEventSink.idl @@ -0,0 +1,104 @@ +/* -*- 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! + * + * NOTE: originalURI isn't yet set on the new channel when + * asyncOnChannelRedirect is called. + * + * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback() + * + * @param oldChannel + * The channel that's being redirected. + * @param newChannel + * The new channel. This channel is not opened yet. + * @param flags + * Flags indicating the type of redirect. A bitmask consisting + * of flags from above. + * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be + * set. + * @param callback + * Object to inform about the async result of this method + * + * @throw Throwing an exception will cause the redirect to be + * cancelled + */ + void asyncOnChannelRedirect(in nsIChannel oldChannel, + in nsIChannel newChannel, + in unsigned long flags, + in nsIAsyncVerifyRedirectCallback callback); +}; diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl new file mode 100644 index 0000000000..3b57013536 --- /dev/null +++ b/netwerk/base/nsIChildChannel.idl @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStreamListener; + +/** + * Implemented by content side of IPC protocols. + */ + +[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)] +interface nsIChildChannel : nsISupports +{ + /** + * Create the chrome side of the IPC protocol and join an existing 'real' + * channel on the parent process. The id is provided by + * nsIRedirectChannelRegistrar on the chrome process and pushed to the child + * protocol as an argument to event starting a redirect. + * + * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created + * child channel, where the new channel is intended to be created on the + * child process. + */ + void connectParent(in uint32_t registrarId); + + /** + * As AsyncOpen is called on the chrome process for redirect target channels, + * we have to inform the child side of the protocol of that fact by a special + * method. + */ + void completeRedirectSetup(in nsIStreamListener aListener); +}; diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl new file mode 100644 index 0000000000..5426eabbcc --- /dev/null +++ b/netwerk/base/nsIClassOfService.idl @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIClassOfService.idl + * + * Used to express class dependencies and characteristics - complimentary to + * nsISupportsPriority which is used to express weight + * + * Channels that implement this interface may make use of this + * information in different ways. + */ + +// convenience class for passing around the class of service +%{C++ +namespace mozilla::net { +class ClassOfService; +} +%} +native ClassOfService(mozilla::net::ClassOfService); + +[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)] +interface nsIClassOfService : nsISupports +{ + attribute unsigned long classFlags; + attribute bool incremental; + + void clearClassFlags(in unsigned long flags); + void addClassFlags(in unsigned long flags); + void setClassOfService(in ClassOfService s); + + // All these flags have a (de)prioritization effect. + + // In the HTTP/1 world the priority is considered for all requests inside a so + // called 'Request Context' which is a context common to all sub-resources + // belonging to a single top level window (RequestContextService). Requests + // marked with the Leader flag are blocking (preventing from being sent to the + // server) all other resource loads except those marked with the Unblocked + // flag. Other classes run in parallel - neither being blocked no ;r blocking. + // The Leader flag is used only for blocking resources (sync and + // defer javascript resources and stylesheets.) Purpose is to deliver these + // first-paint and domcontentloaded blocking resources as soon as possbile. + + // In the HTTP/2 world it's different. Priorities are done only per HTTP/2 + // session, normally we have one session per one origin (including origin + // attributes!) Requests are dispatched (sent) immediately on an HTTP/2 + // session. Each session has artificial streams (groups) relating to the class + // of service flags (Leader, Other, Background, Speculative, Follower, + // UrgentStart), each such a stream is given a different weight (only way to + // give a stream a priority in HTTP/2) reflecting the desired request group + // priority. Actual request streams are then dependent on these artificial + // streams (groups). nsISupportsPriority of each request is passed as a weight + // on the HTTP/2 stream to prioritize streams in the same group. A stream can + // also be dependent on other stream. We have dependency of Followers on + // Leaders, hence everything set the Follower flag should be processed by the + // server after Leaders. Same for Speculative being dependent on Background. The + // tree is created and used here: + // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Session.cpp#1053-1070 + // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Stream.cpp#1338 + // For detailed description of how HTTP/2 server should handle the priorities + // and dependencies see: + // https://developers.google.com/web/fundamentals/performance/http2/#stream_prioritization + // Please note that the dependecies and weights we are sending to the server + // are only suggestions, the server may completely ignore it. + + // Leaders (should) block all other resources except Unblocked. This flag + // also priortizes HTTP cache reading queue by blocking all other cache + // requests. + const unsigned long Leader = 1 << 0; + // The Follower flag is currently unused! + const unsigned long Follower = 1 << 1; + // The Speculative flag is currently unused! + const unsigned long Speculative = 1 << 2; + // The Background flag is currently only used for Beacon. + const unsigned long Background = 1 << 3; + // Requests marked with this flag are not blocked by Leaders. This is mainly + // used for probing-like XMLHttpRequests that may block delivery of head + // blocking resources, e.g. CSS files tailored for the UA. + const unsigned long Unblocked = 1 << 4; + // Throttleable flag allows response throttling of the resource load. Note + // that this functionality is currently disabled. + const unsigned long Throttleable = 1 << 5; + // UrgentStart makes the request temporarily extend HTTP/1 connection + // parallelism limits. Used mainly for navigational requests (top level html) + // and any request considered coming from a user interaction to make reaction + // of the browser as fast as possible and not blocked. + const unsigned long UrgentStart = 1 << 6; + // Specifically disables throttling under any circumstances, used for media + // responses mainly. + const unsigned long DontThrottle = 1 << 7; + // Enforce tailing on this load; any of Leader, Unblocked, UrgentStart, + // TailForbidden overrule this flag (disable tailing.) + const unsigned long Tail = 1 << 8; + // Tailing may be engaged regardless if the load is marked Unblocked when some + // other conditions are met later, like when the load is found to be a + // tracker. + const unsigned long TailAllowed = 1 << 9; + // Tailing not allowed under any circumstances or combination of flags. + const unsigned long TailForbidden = 1 << 10; +}; diff --git a/netwerk/base/nsIClassifiedChannel.idl b/netwerk/base/nsIClassifiedChannel.idl new file mode 100644 index 0000000000..482c524cbf --- /dev/null +++ b/netwerk/base/nsIClassifiedChannel.idl @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIClassifiedChannel + * + * A channel may optionally implement this interface if it carries classified + * result information of channel classifier. The information contains, for + * example, the name of matched table and the name of matched provider. + */ +[builtinclass, scriptable, uuid(70cf6091-a1de-4aa8-8224-058f8964be31)] +interface nsIClassifiedChannel : nsISupports +{ + /** + * Sets matched info of the classified channel. + * + * @param aList + * Name of the Safe Browsing list that matched (e.g. goog-phish-shavar). + * @param aProvider + * Name of the Safe Browsing provider that matched (e.g. google) + * @param aFullHash + * Full hash of URL that matched Safe Browsing list. + */ + void setMatchedInfo(in ACString aList, + in ACString aProvider, + in ACString aFullHash); + + /** + * Name of the list that matched + */ + readonly attribute ACString matchedList; + + /** + * Name of provider that matched + */ + readonly attribute ACString matchedProvider; + + /** + * Full hash of URL that matched + */ + readonly attribute ACString matchedFullHash; + + /** + * Sets matched tracking info of the classified channel. + * + * @param aLists + * Name of the Tracking Protection list that matched (e.g. content-track-digest256). + * @param aFullHash + * Full hash of URLs that matched Tracking Protection list. + */ + void setMatchedTrackingInfo(in Array aLists, + in Array aFullHashes); + + /** + * Name of the lists that matched + */ + readonly attribute Array matchedTrackingLists; + + /** + * Full hash of URLs that matched + */ + readonly attribute Array matchedTrackingFullHashes; + + /** + * Returns the classification flags if the channel has been processed by + * URL-Classifier features and is considered first-party. + */ + [infallible] readonly attribute unsigned long firstPartyClassificationFlags; + + /** + * Returns the classification flags if the channel has been processed by + * URL-Classifier features and is considered third-party with the top + * window URI. + */ + [infallible] readonly attribute unsigned long thirdPartyClassificationFlags; + + /* + * Returns the classification flags if the channel has been processed by + * URL-Classifier features. This value is equal to + * "firstPartyClassificationFlags || thirdPartyClassificationFlags". + * + * Note that top-level channels could be classified as well. + * In order to identify third-party resources specifically, use + * classificationThirdPartyFlags; + */ + [infallible] readonly attribute unsigned long classificationFlags; + + cenum ClassificationFlags : 32 { + /** + * The resource is on the fingerprinting list. + */ + CLASSIFIED_FINGERPRINTING = 0x0001, + CLASSIFIED_FINGERPRINTING_CONTENT = 0x0080, + + /** + * The resource is on the cryptomining list. + */ + CLASSIFIED_CRYPTOMINING = 0x0002, + CLASSIFIED_CRYPTOMINING_CONTENT = 0x0100, + + /** + * The following are about tracking annotation and are available only + * if the privacy.trackingprotection.annotate_channels pref. + * CLASSIFIED_TRACKING is set if we are not able to identify the + * type of classification. + */ + CLASSIFIED_TRACKING = 0x0004, + CLASSIFIED_TRACKING_AD = 0x0008, + CLASSIFIED_TRACKING_ANALYTICS = 0x0010, + CLASSIFIED_TRACKING_SOCIAL = 0x0020, + CLASSIFIED_TRACKING_CONTENT = 0x0040, + + /** + * The following are about social tracking. + */ + CLASSIFIED_SOCIALTRACKING = 0x0200, + CLASSIFIED_SOCIALTRACKING_FACEBOOK = 0x0400, + CLASSIFIED_SOCIALTRACKING_LINKEDIN = 0x0800, + CLASSIFIED_SOCIALTRACKING_TWITTER = 0x1000, + + /** + * The following are about email tracking. + */ + CLASSIFIED_EMAILTRACKING = 0x2000, + CLASSIFIED_EMAILTRACKING_CONTENT = 0x4000, + + /** + * This is exposed to help to identify tracking classification using the + * basic lists. + */ + CLASSIFIED_ANY_BASIC_TRACKING = CLASSIFIED_TRACKING | + CLASSIFIED_TRACKING_AD | CLASSIFIED_TRACKING_ANALYTICS | + CLASSIFIED_TRACKING_SOCIAL | CLASSIFIED_FINGERPRINTING, + + /** + * This is exposed to help to identify tracking classification using the + * strict lists. + */ + CLASSIFIED_ANY_STRICT_TRACKING = CLASSIFIED_ANY_BASIC_TRACKING | + CLASSIFIED_TRACKING_CONTENT | CLASSIFIED_FINGERPRINTING_CONTENT, + + /** + * This is exposed to help to identify social tracking classification + * flags. + */ + CLASSIFIED_ANY_SOCIAL_TRACKING = CLASSIFIED_SOCIALTRACKING | + CLASSIFIED_SOCIALTRACKING_FACEBOOK | + CLASSIFIED_SOCIALTRACKING_LINKEDIN | CLASSIFIED_SOCIALTRACKING_TWITTER, + }; + + /** + * Returns true if the channel has been processed by URL-Classifier features + * and is considered third-party with the top window URI, and if it has loaded + * a resource that is classified as a tracker. + * + * This is a helper attribute which returns the same value of + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_BASIC_TRACKING) or + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_STRICT_TRACKING) or + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING) + */ + boolean isThirdPartyTrackingResource(); + +%{ C++ + inline bool IsThirdPartyTrackingResource() + { + bool value = false; + if (NS_SUCCEEDED(IsThirdPartyTrackingResource(&value)) && value) { + return true; + } + return false; + } +%} + + /** + * Returns true if the channel has loaded a 3rd party resource that is + * classified as a social tracker. + * + * This is a helper attribute which returns the same value of + * (classificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING) + * + * Note that top-level channels could be marked as tracking + * resources. In order to identify third-party social tracking resources + * specifically, check the flags manually or add a new helper here. + */ + boolean isThirdPartySocialTrackingResource(); + +%{ C++ + inline bool IsThirdPartySocialTrackingResource() + { + bool value = false; + if (NS_SUCCEEDED(IsThirdPartySocialTrackingResource(&value)) && value) { + return true; + } + return false; + } +%} +}; diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl new file mode 100644 index 0000000000..f9052b8e60 --- /dev/null +++ b/netwerk/base/nsIContentSniffer.idl @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRequest; + +/** + * Content sniffer interface. Components implementing this interface can + * determine a MIME type from a chunk of bytes. + */ +[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)] +interface nsIContentSniffer : nsISupports +{ + /** + * Given a chunk of data, determines a MIME type. Information from the given + * request may be used in order to make a better decision. + * + * @param aRequest The request where this data came from. May be null. + * @param aData Data to check + * @param aLength Length of the data + * + * @return The content type + * + * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined. + * + * @note Implementations should consider the request read-only. Especially, + * they should not attempt to set the content type property that subclasses of + * nsIRequest might offer. + */ + ACString getMIMETypeFromContent(in nsIRequest aRequest, + [const,array,size_is(aLength)] in octet aData, + in unsigned long aLength); +}; diff --git a/netwerk/base/nsIDHCPClient.idl b/netwerk/base/nsIDHCPClient.idl new file mode 100644 index 0000000000..1d986ebd52 --- /dev/null +++ b/netwerk/base/nsIDHCPClient.idl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface allows the proxy code to access the DHCP Options in a platform-specific way + */ +[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)] +interface nsIDHCPClient : nsISupports +{ + + /** + * returns the DHCP Option designated by the option parameter + */ + ACString getOption(in uint8_t option); +}; diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl new file mode 100644 index 0000000000..ae80a9e458 --- /dev/null +++ b/netwerk/base/nsIDashboard.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* A JavaScript callback function that takes a JSON as its parameter. + * The returned JSON contains arrays with data + */ +[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)] +interface nsINetDashboardCallback : nsISupports +{ + void onDashboardDataAvailable(in jsval data); +}; + +/* The dashboard service. + * The async API returns JSONs, which hold arrays with the required info. + * Only one request of each type may be pending at any time. + */ +[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)] +interface nsIDashboard : nsISupports +{ + /* Arrays: host, port, tcp, active, socksent, sockreceived + * Values: sent, received */ + void requestSockets(in nsINetDashboardCallback cb); + + /* Arrays: host, port, spdy, ssl + * Array of arrays: active, idle */ + void requestHttpConnections(in nsINetDashboardCallback cb); + + /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */ + void requestWebsocketConnections(in nsINetDashboardCallback cb); + + /* Arrays: hostname, family, hostaddr, expiration */ + void requestDNSInfo(in nsINetDashboardCallback cb); + + /* aProtocol: a transport layer protocol: + * ex: "ssl", "tcp", default is "tcp". + * aHost: the host's name + * aPort: the port which the connection will open on + * aTimeout: the timespan before the connection will be timed out */ + void requestConnection(in ACString aHost, in unsigned long aPort, + in string aProtocol, in unsigned long aTimeout, + in nsINetDashboardCallback cb); + + /* When true, the service will log websocket events */ + attribute boolean enableLogging; + + /* DNS resolver for host name + * aHost: host name */ + void requestDNSLookup(in ACString aHost, in nsINetDashboardCallback cb); + + /* Resolve HTTPS RRs for host name + * aHost: host name */ + void requestDNSHTTPSRRLookup(in ACString aHost, + in nsINetDashboardCallback cb); + + /** + * Asyncly returns stats regarding the "Race Cache With Network" feature. + */ + void requestRcwnStats(in nsINetDashboardCallback cb); + + AUTF8String getLogPath(); +}; diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl new file mode 100644 index 0000000000..933caf4371 --- /dev/null +++ b/netwerk/base/nsIDashboardEventNotifier.idl @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)] +interface nsIDashboardEventNotifier : nsISupports +{ + /* These methods are called to register a websocket event with the dashboard + * + * A host is identified by the (aHost, aSerial) pair. + * aHost: the host's name: example.com + * aSerial: a number that uniquely identifies the websocket + * + * aEncrypted: if the connection is encrypted + * aLength: the length of the message in bytes + */ + void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted); + void removeHost(in ACString aHost, in unsigned long aSerial); + void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); + void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); +}; diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl new file mode 100644 index 0000000000..c887c32c74 --- /dev/null +++ b/netwerk/base/nsIDownloader.idl @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +interface nsIFile; +interface nsIDownloadObserver; + +/** + * nsIDownloader + * + * A downloader is a special implementation of a nsIStreamListener that will + * make the contents of the stream available as a file. This may utilize the + * disk cache as an optimization to avoid an extra copy of the data on disk. + * The resulting file is valid from the time the downloader completes until + * the last reference to the downloader is released. + */ +[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)] +interface nsIDownloader : nsIStreamListener +{ + /** + * Initialize this downloader + * + * @param observer + * the observer to be notified when the download completes. + * @param downloadLocation + * the location where the stream contents should be written. + * if null, the downloader will select a location and the + * resulting file will be deleted (or otherwise made invalid) + * when the downloader object is destroyed. if an explicit + * download location is specified then the resulting file will + * not be deleted, and it will be the callers responsibility + * to keep track of the file, etc. + */ + void init(in nsIDownloadObserver observer, + in nsIFile downloadLocation); +}; + +[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)] +interface nsIDownloadObserver : nsISupports +{ + /** + * Called to signal a download that has completed. + */ + void onDownloadComplete(in nsIDownloader downloader, + in nsIRequest request, + in nsresult status, + in nsIFile result); +}; diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl new file mode 100644 index 0000000000..a7571a0a92 --- /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 0000000000..6f86dbb05a --- /dev/null +++ b/netwerk/base/nsIExternalProtocolHandler.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProtocolHandler.idl" + +[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)] +interface nsIExternalProtocolHandler: nsIProtocolHandler +{ + /** + * This method checks if the external handler exists for a given scheme. + * + * @param scheme external scheme. + * @return TRUE if the external handler exists for the input scheme, FALSE otherwise. + */ + boolean externalAppExistsForScheme(in ACString scheme); +}; diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl new file mode 100644 index 0000000000..026fa41bcf --- /dev/null +++ b/netwerk/base/nsIFileStreams.idl @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" +#include "nsIRandomAccessStream.idl" + +interface nsIEventTarget; +interface nsIFile; +interface nsIFileMetadataCallback; + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); + +/** + * An input stream that allows you to read from a file. + */ +[scriptable, builtinclass, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)] +interface nsIFileInputStream : nsIInputStream +{ + /** + * @param file file to read from + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDONLY). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * If this is set, the file will close automatically when the end of the + * file is reached. + */ + const long CLOSE_ON_EOF = 1<<2; + + /** + * If this is set, the file will be reopened whenever we reach the start of + * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative + * seek that happen to reach the beginning of the file. If the file is + * already open and the seek occurs, it will happen naturally. (The file + * will only be reopened if it is closed for some reason.) + */ + const long REOPEN_ON_REWIND = 1<<3; + + /** + * If this is set, the file will be opened (i.e., a call to + * PR_Open done) only when we do an actual operation on the stream, + * or more specifically, when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - ReadLine + * + * DEFER_OPEN is useful if we use the stream on a background + * thread, so that the opening and possible |stat|ing of the file + * happens there as well. + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read. Also, the file is not locked when Init is called, + * so it might be deleted before we try to read from it. + */ + const long DEFER_OPEN = 1<<4; + + /** + * This flag has no effect and is totally ignored on any platform except + * Windows since this is the default behavior on POSIX systems. On Windows + * if this flag is set then the stream is opened in a special mode that + * allows the OS to delete the file from disk just like POSIX. + */ + const long SHARE_DELETE = 1<<5; +}; + +/** + * An output stream that lets you stream to a file. + */ +[scriptable, builtinclass, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)] +interface nsIFileOutputStream : nsIOutputStream +{ + /** + * @param file file to write to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_WRONLY | + * PR_CREATE_FILE | PR_TRUNCATE) + * @param perm file mode bits listed in prio.h or -1 to + * use the default permissions (0664) + * @param behaviorFlags flags specifying various behaviors of the class + * (currently none supported) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * @param length asks the operating system to allocate storage for + * this file of at least |length| bytes long, and + * set the file length to the corresponding size. + * @throws NS_ERROR_FAILURE if the preallocation fails. + * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened. + */ + [noscript] void preallocate(in long long length); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Write + * - Flush + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first write, and if the file is to be created, then it will not + * appear on the disk until the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * A stream that allows you to read from a file or stream to a file. + */ +[scriptable, builtinclass, uuid(82cf605a-8393-4550-83ab-43cd5578e006)] +interface nsIFileRandomAccessStream : nsIRandomAccessStream +{ + /** + * @param file file to read from or stream to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDWR). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - Flush + * - Write + * - GetSize + * - GetLastModified + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read or write. The file is not locked when Init is called, + * so it might be deleted before we try to read from it and if the + * file is to be created, then it will not appear on the disk until + * the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * An interface that allows you to get some metadata like file size and + * file last modified time. These methods and attributes can throw + * NS_BASE_STREAM_WOULD_BLOCK in case the informations are not available yet. + * If this happens, consider the use of nsIAsyncFileMetadata. + * + * If using nsIAsyncFileMetadata, you should retrieve any data via this + * interface before taking any action that might consume the underlying stream. + * For example, once Available(), Read(), or nsIAsyncInputStream::AsyncWait() + * are invoked, these methods may return NS_BASE_STREAM_CLOSED. This will + * happen when using RemoteLazyInputStream with an underlying file stream, for + * example. + */ +[scriptable, builtinclass, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)] +interface nsIFileMetadata : nsISupports +{ + /** + * File size in bytes. + */ + readonly attribute long long size; + + /** + * File last modified time in milliseconds from midnight (00:00:00), + * January 1, 1970 Greenwich Mean Time (GMT). + */ + readonly attribute long long lastModified; + + /** + * The internal file descriptor. It can be used for memory mapping of the + * underlying file. Please use carefully! If this returns + * NS_BASE_STREAM_WOULD_BLOCK, consider the use of nsIAsyncFileMetadata. + */ + [noscript] PRFileDescPtr getFileDescriptor(); +}; + +[scriptable, builtinclass, uuid(de15b80b-29ba-4b7f-9220-a3d75b17ae8c)] +interface nsIAsyncFileMetadata : nsIFileMetadata +{ + /** + * Asynchronously wait for the object to be ready. + * + * @param aCallback The callback will be used when the stream is ready to + * return File metadata. Use a nullptr to cancel a + * previous operation. + * + * @param aEventTarget The event target where aCallback will be executed. + * If aCallback is passed, aEventTarget cannot be null. + */ + void asyncFileMetadataWait(in nsIFileMetadataCallback aCallback, + in nsIEventTarget aEventTarget); +}; + +/** + * This is a companion interface for + * nsIAsyncFileMetadata::asyncFileMetadataWait. + */ +[function, scriptable, uuid(d01c7ead-7ba3-4726-b399-618ec8ec7057)] +interface nsIFileMetadataCallback : nsISupports +{ + /** + * Called to indicate that the nsIFileMetadata object is ready. + */ + void onFileMetadataReady(in nsIAsyncFileMetadata aObject); +}; diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl new file mode 100644 index 0000000000..3c97a2e3af --- /dev/null +++ b/netwerk/base/nsIFileURL.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURL.idl" + +interface nsIFile; +interface nsIURIMutator; + +/** + * nsIFileURL provides access to the underlying nsIFile object corresponding to + * an URL. The URL scheme need not be file:, since other local protocols may + * map URLs to files (e.g., resource:). + */ +[scriptable, builtinclass, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)] +interface nsIFileURL : nsIURL +{ + /** + * Get the nsIFile corresponding to this URL. + * + * - Returns a reference to an immutable object. Callers must clone + * before attempting to modify the returned nsIFile object. NOTE: this + * constraint might not be enforced at runtime, so beware!! + */ + readonly attribute nsIFile file; +}; + +[scriptable, builtinclass, uuid(a588b6f2-d2b9-4024-84c7-be3368546b57)] +interface nsIFileURLMutator : nsISupports +{ + /* + * - Marks the inner URI implementation as one that supports nsIFileURL. + */ + [must_use, noscript] void markFileURL(); + + /* + * - Setter clones the nsIFile object (allowing the caller to safely modify + * the nsIFile object after setting it on this interface). + */ + [must_use, noscript] void setFile(in nsIFile aFile); +}; diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl new file mode 100644 index 0000000000..714c83f52d --- /dev/null +++ b/netwerk/base/nsIForcePendingChannel.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIForcePending interface exposes a function that enables overwriting of the normal + * behavior for the channel's IsPending(), forcing 'true' to be returned. + */ + +[uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)] +interface nsIForcePendingChannel : nsISupports +{ + + /** + * forcePending(true) overrides the normal behavior for the + * channel's IsPending(), forcing 'true' to be returned. A call to + * forcePending(false) reverts IsPending() back to normal behavior. + */ + void forcePending(in boolean aForcePending); +}; diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl new file mode 100644 index 0000000000..870886390c --- /dev/null +++ b/netwerk/base/nsIFormPOSTActionChannel.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIUploadChannel.idl" + +/** + * nsIFormPOSTActionChannel + * + * Channel classes that want to be allowed for HTML form POST action must + * implement this interface. + */ +[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)] +interface nsIFormPOSTActionChannel : nsIUploadChannel +{ +}; diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl new file mode 100644 index 0000000000..74fd3c97ab --- /dev/null +++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)] +interface nsIHttpAuthenticatorCallback : nsISupports +{ + /** + * Authentication data for a header is available. + * + * @param aCreds + * Credentials which were obtained asynchonously. + * @param aFlags + * Flags set by asynchronous call. + * @param aResult + * Result status of credentials generation + * @param aSessionState + * Modified session state to be passed to caller + * @param aContinuationState + * Modified continuation state to be passed to caller + */ + void onCredsGenerated(in ACString aCreds, + in unsigned long aFlags, + in nsresult aResult, + in nsISupports aSessionsState, + in nsISupports aContinuationState); + +}; diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl new file mode 100644 index 0000000000..f44605c000 --- /dev/null +++ b/netwerk/base/nsIHttpPushListener.idl @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIHttpChannel; + +/** + * nsIHttpPushListener + * + * Used for triggering when a HTTP/2 push is received. + * + */ +[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)] +interface nsIHttpPushListener : nsISupports +{ + /** + * When provided as a notificationCallback to an httpChannel, this.onPush() + * will be invoked when there is a >= Http2 push to that + * channel. The push may be in progress. + * + * The consumer must start the new channel in the usual way by calling + * pushChannel.AsyncOpen with a nsIStreamListener object that + * will receive the normal sequence of OnStartRequest(), + * 0 to N OnDataAvailable(), and onStopRequest(). + * + * The new channel can be canceled after the AsyncOpen if it is not wanted. + * + * @param associatedChannel + * the monitor channel that was recieved on + * @param pushChannel + * a channel to the resource which is being pushed + */ + void onPush(in nsIHttpChannel associatedChannel, + in nsIHttpChannel pushChannel); +}; diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl new file mode 100644 index 0000000000..6a4d58ceab --- /dev/null +++ b/netwerk/base/nsIIOService.idl @@ -0,0 +1,354 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsIProtocolHandler; +interface nsIChannel; +interface nsIURI; +interface nsIFile; +interface nsIPrincipal; +interface nsILoadInfo; +interface nsIWebTransport; + +webidl Node; + +%{C++ +#include "mozilla/Maybe.h" + +namespace mozilla { +namespace dom { +class ClientInfo; +class ServiceWorkerDescriptor; +} // namespace dom +} // namespace mozilla +%} + +[ref] native const_MaybeClientInfoRef(const mozilla::Maybe); +[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe); + +/** + * nsIIOService provides a set of network utility functions. This interface + * duplicates many of the nsIProtocolHandler methods in a protocol handler + * independent way (e.g., NewURI inspects the scheme in order to delegate + * creation of the new URI to the appropriate protocol handler). nsIIOService + * also provides a set of URL parsing utility functions. These are provided + * as a convenience to the programmer and in some cases to improve performance + * by eliminating intermediate data structures and interfaces. + */ +[scriptable, builtinclass, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)] +interface nsIIOService : nsISupports +{ + /** + * Returns a protocol handler for a given URI scheme. + * + * @param aScheme the URI scheme + * @return reference to corresponding nsIProtocolHandler + */ + nsIProtocolHandler getProtocolHandler(in string aScheme); + + /** + * Returns the protocol flags for a given scheme. + * + * @param aScheme the URI scheme + * @return protocol flags for the corresponding protocol + */ + unsigned long getProtocolFlags(in string aScheme); + + /** + * Returns the dynamic protocol flags for a given URI. + * + * @param aURI the URI to get all dynamic flags for + * @return protocol flags for that URI + */ + unsigned long getDynamicProtocolFlags(in nsIURI aURI); + + /** + * Returns the default port for a given scheme. + * + * @param aScheme the URI scheme + * @return default port for the corresponding protocol + */ + long getDefaultPort(in string aScheme); + + /** + * This method constructs a new URI based on the scheme of the URI spec. + * QueryInterface can be used on the resulting URI object to obtain a more + * specific type of URI. + */ + nsIURI newURI(in AUTF8String aSpec, + [optional] in string aOriginCharset, + [optional] in nsIURI aBaseURI); + + /** + * This method constructs a new URI from a nsIFile. + * + * @param aFile specifies the file path + * @return reference to a new nsIURI object + * + * Note: in the future, for perf reasons we should allow + * callers to specify whether this is a file or directory by + * splitting this into newDirURI() and newActualFileURI(). + */ + nsIURI newFileURI(in nsIFile aFile); + + /** + * Converts an internal URI (e.g. one that has a username and password in + * it) into one which we can expose to the user, for example on the URL bar. + * + * @param aURI The URI to be converted. + * @return nsIURI The converted, exposable URI. + */ + nsIURI createExposableURI(in nsIURI aURI); + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + */ + nsIChannel newChannelFromURI(in nsIURI aURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + [noscript, nostdcall, notxpcom] + nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in const_MaybeClientInfoRef aLoadingClientInfo, + in const_MaybeServiceWorkerDescriptorRef aController, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType, + in unsigned long aSandboxFlags, + in boolean aSkipCheckForBrokenURLOrZeroSized, + out nsIChannel aResult); + + /** + * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...) + */ + nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, + in nsILoadInfo aLoadInfo); + + /** + * Equivalent to newChannelFromURI(newURI(...)) + */ + nsIChannel newChannel(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + /** + * Creates a WebTransport. + */ + nsIWebTransport newWebTransport(); + + /** + * Returns true if networking is in "offline" mode. When in offline mode, + * attempts to access the network will fail (although this does not + * necessarily correlate with whether there is actually a network + * available -- that's hard to detect without causing the dialer to + * come up). + * + * Changing this fires observer notifications ... see below. + */ + attribute boolean offline; + + /** + * Returns false if there are no interfaces for a network request + */ + readonly attribute boolean connectivity; + + /** + * Checks if a port number is banned. This involves consulting a list of + * unsafe ports, corresponding to network services that may be easily + * exploitable. If the given port is considered unsafe, then the protocol + * handler (corresponding to aScheme) will be asked whether it wishes to + * override the IO service's decision to block the port. This gives the + * protocol handler ultimate control over its own security policy while + * ensuring reasonable, default protection. + * + * @see nsIProtocolHandler::allowPort + */ + boolean allowPort(in long aPort, in string aScheme); + + /** + * Utility to extract the scheme from a URL string, consistently and + * according to spec (see RFC 2396). + * + * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme + * can also be extracted from a URL string via nsIURI. This method + * is provided purely as an optimization. + * + * @param aSpec the URL string to parse + * @return URL scheme, lowercase + * + * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form. + */ + ACString extractScheme(in AUTF8String urlString); + + /** + * Checks if a URI host is a local IPv4 or IPv6 address literal. + * + * @param nsIURI the URI that contains the hostname to check + * @return true if the URI hostname is a local IP address + */ + boolean hostnameIsLocalIPAddress(in nsIURI aURI); + + /** + * Checks if a URI host is a shared IPv4 address literal. + * + * @param nsIURI the URI that contains the hostname to check + * @return true if the URI hostname is a shared IP address + */ + boolean hostnameIsSharedIPAddress(in nsIURI aURI); + + /** + * While this is set, IOService will monitor an nsINetworkLinkService + * (if available) and set its offline status to "true" whenever + * isLinkUp is false. + * + * Applications that want to control changes to the IOService's offline + * status should set this to false, watch for network:link-status-changed + * broadcasts, and change nsIIOService::offline as they see fit. Note + * that this means during application startup, IOService may be offline + * if there is no link, until application code runs and can turn off + * this management. + */ + attribute boolean manageOfflineStatus; + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aProxyURI + * nsIURI to use for proxy resolution. Can be null in which + * case aURI is used + * @param aProxyFlags flags from nsIProtocolProxyService to use + * when resolving proxies for this new channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + */ + nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + /** + * Return true if socket process is launched. + */ + readonly attribute boolean socketProcessLaunched; + + /** + * The pid for socket process. + */ + readonly attribute unsigned long long socketProcessId; + + /** + * Register a protocol handler at runtime, given protocol flags and a + * default port. + * + * Statically registered protocol handlers cannot be overridden, and an + * error will be returned if that is attempted. + * + * Runtime registered protocol handlers are never QueryInterface-ed into + * `nsIProtocolHandlerWithDynamicFlags`, so that interface will be ignored. + * + * @param aScheme the scheme handled by the protocol handler. + * @param aHandler the protocol handler instance. + * @param aProtocolFlags protocol flags for this protocol, see + * nsIProtocolHandler for values. + * @param aDefaultPort default port for this scheme, or -1. + */ + void registerProtocolHandler(in ACString aScheme, + in nsIProtocolHandler aHandler, + in unsigned long aProtocolFlags, + in long aDefaultPort); + + /** + * Unregister a protocol handler which was previously registered using + * registerProtocolHandler. + * + * @param aScheme the scheme to unregister a handler for. + */ + void unregisterProtocolHandler(in ACString aScheme); +}; + +%{C++ +/** + * We send notifications through nsIObserverService with topic + * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE + * when 'offline' has changed from false to true, and we are about + * to shut down network services such as DNS. When those + * services have been shut down, we send a notification with + * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_OFFLINE. + * + * When 'offline' changes from true to false, then after + * network services have been restarted, we send a notification + * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_ONLINE. + */ +#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline" +#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed" +#define NS_IOSERVICE_OFFLINE "offline" +#define NS_IOSERVICE_ONLINE "online" + +%} + +[uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)] +interface nsIIOServiceInternal : nsISupports +{ + /** + * This is an internal method that should only be called from ContentChild + * in order to pass the connectivity state from the chrome process to the + * content process. It throws if called outside the content process. + */ + void SetConnectivity(in boolean connectivity); + + /** + * An internal method to asynchronously run our notifications that happen + * when we wake from sleep + */ + void NotifyWakeup(); +}; diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl new file mode 100644 index 0000000000..3ae363c488 --- /dev/null +++ b/netwerk/base/nsIIncrementalDownload.idl @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIURI; +interface nsIFile; +interface nsIRequestObserver; + +/** + * An incremental download object attempts to fetch a file piecemeal over time + * in an effort to minimize network bandwidth usage. + * + * Canceling a background download does not cause the file on disk to be + * deleted. + */ +[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)] +interface nsIIncrementalDownload : nsIRequest +{ + /** + * Initialize the incremental download object. If the destination file + * already exists, then only the remaining portion of the file will be + * fetched. + * + * NOTE: The downloader will create the destination file if it does not + * already exist. It will create the file with the permissions 0600 if + * needed. To affect the permissions of the file, consumers of this + * interface may create an empty file at the specified destination prior to + * starting the incremental download. + * + * NOTE: Since this class may create a temporary file at the specified + * destination, it is advisable for the consumer of this interface to specify + * a file name for the destination that would not tempt the user into + * double-clicking it. For example, it might be wise to append a file + * extension like ".part" to the end of the destination to protect users from + * accidentally running "blah.exe" before it is a complete file. + * + * @param uri + * The URI to fetch. + * @param destination + * The location where the file is to be stored. + * @param chunkSize + * The size of the chunks to fetch. A non-positive value results in + * the default chunk size being used. + * @param intervalInSeconds + * The amount of time to wait between fetching chunks. Pass a + * negative to use the default interval, or 0 to fetch the remaining + * part of the file in one chunk. + */ + void init(in nsIURI uri, in nsIFile destination, in long chunkSize, + in long intervalInSeconds); + + /** + * The URI being fetched. + */ + readonly attribute nsIURI URI; + + /** + * The URI being fetched after any redirects have been followed. This + * attribute is set just prior to calling OnStartRequest on the observer + * passed to the start method. + */ + readonly attribute nsIURI finalURI; + + /** + * The file where the download is being written. + */ + readonly attribute nsIFile destination; + + /** + * The total number of bytes for the requested file. This attribute is set + * just prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the total size is unknown. + */ + readonly attribute long long totalSize; + + /** + * The current number of bytes downloaded so far. This attribute is set just + * prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the current size is unknown. + */ + readonly attribute long long currentSize; + + /** + * Start the incremental download. + * + * @param observer + * An observer to be notified of various events. OnStartRequest is + * called when finalURI and totalSize have been determined or when an + * error occurs. OnStopRequest is called when the file is completely + * downloaded or when an error occurs. If this object implements + * nsIProgressEventSink, then its OnProgress method will be called as + * data is written to the destination file. If this object implements + * nsIInterfaceRequestor, then it will be assigned as the underlying + * channel's notification callbacks, which allows it to provide a + * nsIAuthPrompt implementation if needed by the channel, for example. + * @param ctxt + * User defined object forwarded to the observer's methods. + */ + void start(in nsIRequestObserver observer, + in nsISupports ctxt); +}; diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl new file mode 100644 index 0000000000..60aa9cfef5 --- /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 0000000000..9172a2b283 --- /dev/null +++ b/netwerk/base/nsIInputStreamChannel.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIURI; + +/** + * nsIInputStreamChannel + * + * This interface provides methods to initialize an input stream channel. + * The input stream channel serves as a data pump for an input stream. + */ +[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)] +interface nsIInputStreamChannel : nsISupports +{ + /** + * Sets the URI for this channel. This must be called before the + * channel is opened, and it may only be called once. + */ + void setURI(in nsIURI aURI); + + /** + * Get/set the content stream + * + * This stream contains the data that will be pushed to the channel's + * stream listener. If the stream is non-blocking and supports the + * nsIAsyncInputStream interface, then the stream will be read directly. + * Otherwise, the stream will be read on a background thread. + * + * This attribute must be set before the channel is opened, and it may + * only be set once. + * + * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel + * has been opened. + */ + attribute nsIInputStream contentStream; + + /** + * Get/set the srcdoc data string. When the input stream channel is + * created to load a srcdoc iframe, this is set to hold the value of the + * srcdoc attribute. + * + * This should be the same value used to create contentStream, but this is + * not checked. + * + * Changing the value of this attribute will not otherwise affect the + * functionality of the channel or input stream. + */ + attribute AString srcdocData; + + /** + * Returns true if srcdocData has been set within the channel. + */ + readonly attribute boolean isSrcdocChannel; + + /** + * The base URI to be used for the channel. Used when the base URI cannot + * be inferred by other means, for example when this is a srcdoc channel. + */ + attribute nsIURI baseURI; +}; diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl new file mode 100644 index 0000000000..2bd1ee1683 --- /dev/null +++ b/netwerk/base/nsIInputStreamPump.idl @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsISerialEventTarget; +interface nsIStreamListener; + +/** + * nsIInputStreamPump + * + * This interface provides a means to configure and use a input stream pump + * instance. The input stream pump will asynchronously read from an input + * stream, and push data to an nsIStreamListener instance. It utilizes the + * current thread's nsIEventTarget in order to make reading from the stream + * asynchronous. A different thread can be used if the pump also implements + * nsIThreadRetargetableRequest. + * + * If the given stream supports nsIAsyncInputStream, then the stream pump will + * call the stream's AsyncWait method to drive the stream listener. Otherwise, + * the stream will be read on a background thread utilizing the stream + * transport service. More details are provided below. + */ +[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)] +interface nsIInputStreamPump : nsIRequest +{ + /** + * Initialize the input stream pump. + * + * @param aStream + * contains the data to be read. if the input stream is non-blocking, + * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds + * then the stream will be read directly. otherwise, it will be read + * on a background thread using the stream transport service. + * @param aSegmentSize + * if the stream transport service is used, then this parameter + * specifies the segment size for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aSegmentCount + * if the stream transport service is used, then this parameter + * specifies the segment count for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aCloseWhenDone + * if true, the input stream will be closed after it has been read. + * @param aMainThreadTarget + * a labeled main therad event target. + */ + void init(in nsIInputStream aStream, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount, + in boolean aCloseWhenDone, + [optional] in nsISerialEventTarget aMainThreadTarget); + + /** + * asyncRead causes the input stream to be read in chunks and delivered + * asynchronously to the listener via OnDataAvailable. + * + * @param aListener + * receives notifications. + * @param aListenerContext + * passed to listener methods. + */ + void asyncRead(in nsIStreamListener aListener); +}; diff --git a/netwerk/base/nsIInterceptionInfo.idl b/netwerk/base/nsIInterceptionInfo.idl new file mode 100644 index 0000000000..d3c1b030ac --- /dev/null +++ b/netwerk/base/nsIInterceptionInfo.idl @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsIPrincipal; +interface nsIRedirectHistoryEntry; + +%{C++ +#include "nsTArray.h" +%} + +[ref] native nsIRedirectHistoryEntryArray(const nsTArray>); +/** + * nsIInterceptionInfo is used to record the needed information of the + * InterceptedHttpChannel. + * This infomration need to be propagated to the new channel which created by + * FetchEvent.request or ServiceWorker NavigationPreload. + */ +[scriptable, builtinclass, uuid(8b9cd81f-3cd1-4f6a-9086-92a9bbf055f4)] +interface nsIInterceptionInfo : nsISupports +{ + /** + * InterceptedHttpChannel's triggering principal + */ + [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)] + nsIPrincipal binaryTriggeringPrincipal(); + + [noscript, notxpcom, nostdcall, binaryname(SetTriggeringPrincipal)] + void binarySetTriggeringPrincipal(in nsIPrincipal aPrincipal); + + /** + * InterceptedHttpChannel's content policy type + */ + [noscript, notxpcom, nostdcall, binaryname(ContentPolicyType)] + nsContentPolicyType binaryContentPolicyType(); + + [noscript, notxpcom, nostdcall, binaryname(ExternalContentPolicyType)] + nsContentPolicyType binaryExternalContentPolicyType(); + + [noscript, notxpcom, nostdcall, binaryname(SetContentPolicyType)] + void binarySetContentPolicyType(in nsContentPolicyType aContentPolicyType); + +%{ C++ + inline ExtContentPolicyType GetExtContentPolicyType() + { + return static_cast(ExternalContentPolicyType()); + } +%} + + /** + * The InterceptedHttpChannel's redirect chain + */ + [noscript, notxpcom, nostdcall, binaryname(RedirectChain)] + nsIRedirectHistoryEntryArray binaryRedirectChain(); + + [noscript, notxpcom, nostdcall, binaryname(SetRedirectChain)] + void binarySetRedirectChain( + in nsIRedirectHistoryEntryArray aRedirectChain); + + /** + * The InterceptedHttpChannel is a third party channel or not. + */ + [noscript, notxpcom, nostdcall, binaryname(FromThirdParty)] + bool binaryFromThirdParty(); + + [noscript, notxpcom, nostdcall, binaryname(SetFromThirdParty)] + void binarySetFromThirdParty(in bool aFromThirdParty); +}; diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl new file mode 100644 index 0000000000..bbf2b5d48a --- /dev/null +++ b/netwerk/base/nsILoadContextInfo.idl @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#include "mozilla/BasePrincipal.h" +%} +native OriginAttributesNativePtr(const mozilla::OriginAttributes*); + +interface nsILoadContext; +interface nsIDOMWindow; + +/** + * Helper interface to carry informatin about the load context + * encapsulating origin attributes and IsAnonymous, IsPrivite properties. + * It shall be used where nsILoadContext cannot be used or is not + * available. + */ + +[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)] +interface nsILoadContextInfo : nsISupports +{ + /** + * Whether the context is in a Private Browsing mode + */ + readonly attribute boolean isPrivate; + + /** + * Whether the load is initiated as anonymous + */ + readonly attribute boolean isAnonymous; + + /** + * OriginAttributes hiding all the security context attributes + */ + [implicit_jscontext] + readonly attribute jsval originAttributes; + [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)] + OriginAttributesNativePtr binaryOriginAttributesPtr(); + +%{C++ + /** + * De-XPCOMed getters + */ + bool IsPrivate() + { + bool pb; + GetIsPrivate(&pb); + return pb; + } + + bool IsAnonymous() + { + bool anon; + GetIsAnonymous(&anon); + return anon; + } + + bool Equals(nsILoadContextInfo *aOther) + { + return IsAnonymous() == aOther->IsAnonymous() && + *OriginAttributesPtr() == *aOther->OriginAttributesPtr(); + } +%} +}; + +/** + * Since OriginAttributes struct limits the implementation of + * nsILoadContextInfo (that needs to be thread safe) to C++, + * we need a scriptable factory to create instances of that + * interface from JS. + */ +[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)] +interface nsILoadContextInfoFactory : nsISupports +{ + readonly attribute nsILoadContextInfo default; + readonly attribute nsILoadContextInfo private; + readonly attribute nsILoadContextInfo anonymous; + [implicit_jscontext] + nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes); + nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous); + nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous); +}; diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl new file mode 100644 index 0000000000..06e51e442b --- /dev/null +++ b/netwerk/base/nsILoadGroup.idl @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsISimpleEnumerator; +interface nsIRequestObserver; +interface nsIInterfaceRequestor; +interface nsIRequestContext; + +/** + * A load group maintains a collection of nsIRequest objects. + * This is used in lots of places where groups of requests need to be tracked. + * For example, Document::mDocumentLoadGroup is used to track all requests + * made for subdocuments in order to track page load progress and allow all + * requests made on behalf of the document to be stopped, etc. + */ +[builtinclass, scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)] +interface nsILoadGroup : nsIRequest +{ + /** + * The group observer is notified when requests are added to and removed + * from this load group. The groupObserver is weak referenced. + */ + attribute nsIRequestObserver groupObserver; + + /** + * Accesses the default load request for the group. Each time a number + * of requests are added to a group, the defaultLoadRequest may be set + * to indicate that all of the requests are related to a base request. + * + * The load group inherits its load flags from the default load request. + * If the default load request is NULL, then the group's load flags are + * not changed. + */ + attribute nsIRequest defaultLoadRequest; + + /** + * Adds a new request to the group. This will cause the default load + * flags to be applied to the request. If this is a foreground + * request then the groupObserver's onStartRequest will be called. + * + * If the request is the default load request or if the default load + * request is null, then the load group will inherit its load flags from + * the request. + */ + void addRequest(in nsIRequest aRequest, + in nsISupports aContext); + + /** + * Removes a request from the group. If this is a foreground request + * then the groupObserver's onStopRequest will be called. + * + * By the time this call ends, aRequest will have been removed from the + * loadgroup, even if this function throws an exception. + */ + void removeRequest(in nsIRequest aRequest, + in nsISupports aContext, + in nsresult aStatus); + + /** + * Returns the requests contained directly in this group. + * Enumerator element type: nsIRequest. + */ + readonly attribute nsISimpleEnumerator requests; + + /** + * Returns the count of "active" requests (ie. requests without the + * LOAD_BACKGROUND bit set). + */ + readonly attribute unsigned long activeCount; + + /** + * Notification callbacks for the load group. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Context for managing things like js/css connection blocking, + * and per-tab connection grouping. + */ + readonly attribute unsigned long long requestContextID; + + /** + * The set of load flags that will be added to all new requests added to + * this group. Any existing requests in the load group are not modified, + * so it is expected these flags will be added before requests are added + * to the group - typically via nsIDocShell::defaultLoadFlags on a new + * docShell. + * Note that these flags are *not* added to the default request for the + * load group; it is expected the default request will already have these + * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before + * the docShell has created the default request.) + */ + attribute nsLoadFlags defaultLoadFlags; + + /** + * Returns true if the loadGroup belongs to a discarded context, such as, a + * terminated private browsing session. + */ + [infallible] + readonly attribute boolean isBrowsingContextDiscarded; +}; diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl new file mode 100644 index 0000000000..959913a474 --- /dev/null +++ b/netwerk/base/nsILoadGroupChild.idl @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsILoadGroup; + +/** + * nsILoadGroupChild provides a hierarchy of load groups so that the + * root load group can be used to conceptually tie a series of loading + * operations into a logical whole while still leaving them separate + * for the purposes of cancellation and status events. + */ + +[builtinclass, scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)] +interface nsILoadGroupChild : nsISupports +{ + /** + * The parent of this load group. It is stored with + * a nsIWeakReference/nsWeakPtr so there is no requirement for the + * parentLoadGroup to out live the child, nor will the child keep a + * reference count on the parent. + */ + attribute nsILoadGroup parentLoadGroup; + + /** + * The nsILoadGroup associated with this nsILoadGroupChild + */ + readonly attribute nsILoadGroup childLoadGroup; + + /** + * The rootLoadGroup is the recursive parent of this + * load group where parent is defined as parentlLoadGroup if set + * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes + * precedence.) The nsILoadGroup child is the root if neither parent + * nor loadgroup attribute is specified. + */ + readonly attribute nsILoadGroup rootLoadGroup; +}; diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl new file mode 100644 index 0000000000..79fdcc5e4d --- /dev/null +++ b/netwerk/base/nsILoadInfo.idl @@ -0,0 +1,1488 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" +#include "nsIScriptSecurityManager.idl" +#include "nsIInterceptionInfo.idl" + +interface nsIChannel; +interface nsIContentSecurityPolicy; +interface nsICookieJarSettings; +interface nsICSPEventListener; +interface nsINode; +interface nsIPrincipal; +interface nsIRedirectHistoryEntry; +interface nsIURI; +webidl Document; +webidl BrowsingContext; +native LoadContextRef(already_AddRefed); +%{C++ +#include "nsTArray.h" +#include "mozilla/LoadTainting.h" +#include "mozilla/OriginAttributes.h" +#include "mozilla/UniquePtr.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace dom { +class ClientInfo; +class ClientSource; +class PerformanceStorage; +class ServiceWorkerDescriptor; +} // namespace dom +} // namespace mozilla +%} + +[ref] native nsIRedirectHistoryEntryArray(const nsTArray>); +native OriginAttributes(mozilla::OriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes); +[ref] native CStringArrayRef(const nsTArray); +[ref] native StringArrayRef(const nsTArray); +[ref] native Uint64ArrayRef(const nsTArray); +[ref] native PrincipalArrayRef(const nsTArray>); +[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo); + native UniqueClientSource(mozilla::UniquePtr); + native UniqueClientSourceMove(mozilla::UniquePtr&&); +[ref] native const_MaybeClientInfoRef(const mozilla::Maybe); +[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor); +[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe); +[ptr] native PerformanceStoragePtr(mozilla::dom::PerformanceStorage); + native LoadTainting(mozilla::LoadTainting); + native CSPRef(already_AddRefed); + +typedef unsigned long nsSecurityFlags; + +/** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ +[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)] +interface nsILoadInfo : nsISupports +{ + /** + * The following five flags determine the security mode and hence what kind of + * security checks should be performed throughout the lifetime of the channel. + * + * * SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT + * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + * * SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT + * * SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL + * * SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT + * + * Exactly one of these flags are required to be set in order to allow + * the channel to perform the correct security checks (SOP, CORS, ...) and + * return the correct result principal. If none or more than one of these + * flags are set AsyncOpen will fail. + */ + + /** + * Warning: Never use this flag when creating a new channel! + * Only use this flag if you have to create a temporary LoadInfo + * for performing an explicit nsIContentPolicy check, like e.g. + * when loading something from the cache that needs an explicit + * nsIContentPolicy check. In all other cases pick one of the + * security flags underneath. + */ + const unsigned long SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK = 0; + + /* + * Enforce the same origin policy where loads inherit the principal. + * See the documentation for principalToInherit, which describes exactly what + * principal is inherited. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT = (1<<0); + + /* + * Enforce the same origin policy and data: loads are blocked. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1); + + /** + * Allow loads from other origins. Loads which inherit the principal should + * see the documentation for principalToInherit, which describes exactly what + * principal is inherited. + * + * Commonly used by plain ,