From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- netwerk/base/ADivertableParentChannel.h | 45 + netwerk/base/ARefBase.h | 32 + netwerk/base/ArrayBufferInputStream.cpp | 120 + netwerk/base/ArrayBufferInputStream.h | 38 + netwerk/base/AutoClose.h | 77 + netwerk/base/BackgroundFileSaver.cpp | 1273 +++++++ netwerk/base/BackgroundFileSaver.h | 418 +++ netwerk/base/CaptivePortalService.cpp | 366 ++ netwerk/base/CaptivePortalService.h | 70 + netwerk/base/ChannelDiverterChild.cpp | 27 + netwerk/base/ChannelDiverterChild.h | 26 + netwerk/base/ChannelDiverterParent.cpp | 75 + netwerk/base/ChannelDiverterParent.h | 40 + netwerk/base/Dashboard.cpp | 921 +++++ netwerk/base/Dashboard.h | 105 + netwerk/base/DashboardTypes.h | 63 + netwerk/base/EventTokenBucket.cpp | 462 +++ netwerk/base/EventTokenBucket.h | 149 + netwerk/base/LoadContextInfo.cpp | 181 + netwerk/base/LoadContextInfo.h | 61 + netwerk/base/LoadInfo.cpp | 925 +++++ netwerk/base/LoadInfo.h | 163 + netwerk/base/LoadTainting.h | 43 + netwerk/base/MemoryDownloader.cpp | 92 + netwerk/base/MemoryDownloader.h | 65 + netwerk/base/NetStatistics.h | 100 + netwerk/base/NetUtil.jsm | 461 +++ netwerk/base/NetworkActivityMonitor.cpp | 300 ++ netwerk/base/NetworkActivityMonitor.h | 46 + netwerk/base/NetworkInfoServiceCocoa.cpp | 104 + netwerk/base/NetworkInfoServiceImpl.h | 18 + netwerk/base/NetworkInfoServiceLinux.cpp | 104 + netwerk/base/NetworkInfoServiceWindows.cpp | 60 + netwerk/base/PollableEvent.cpp | 347 ++ netwerk/base/PollableEvent.h | 38 + netwerk/base/Predictor.cpp | 2550 ++++++++++++++ netwerk/base/Predictor.h | 480 +++ netwerk/base/PrivateBrowsingChannel.h | 132 + netwerk/base/ProxyAutoConfig.cpp | 1015 ++++++ netwerk/base/ProxyAutoConfig.h | 104 + netwerk/base/RedirectChannelRegistrar.cpp | 87 + netwerk/base/RedirectChannelRegistrar.h | 44 + netwerk/base/ReferrerPolicy.h | 186 + netwerk/base/RequestContextService.cpp | 217 ++ netwerk/base/RequestContextService.h | 47 + netwerk/base/ShutdownLayer.cpp | 80 + netwerk/base/ShutdownLayer.h | 22 + netwerk/base/SimpleBuffer.cpp | 95 + netwerk/base/SimpleBuffer.h | 57 + netwerk/base/StreamingProtocolService.cpp | 72 + netwerk/base/StreamingProtocolService.h | 36 + netwerk/base/TLSServerSocket.cpp | 515 +++ netwerk/base/TLSServerSocket.h | 81 + netwerk/base/ThrottleQueue.cpp | 392 +++ netwerk/base/ThrottleQueue.h | 65 + netwerk/base/Tickler.cpp | 277 ++ netwerk/base/Tickler.h | 131 + netwerk/base/moz.build | 316 ++ netwerk/base/mozIThirdPartyUtil.idl | 167 + netwerk/base/netCore.h | 14 + netwerk/base/nsASocketHandler.h | 96 + netwerk/base/nsAsyncRedirectVerifyHelper.cpp | 288 ++ netwerk/base/nsAsyncRedirectVerifyHelper.h | 129 + netwerk/base/nsAsyncStreamCopier.cpp | 419 +++ netwerk/base/nsAsyncStreamCopier.h | 83 + netwerk/base/nsAuthInformationHolder.cpp | 74 + netwerk/base/nsAuthInformationHolder.h | 48 + netwerk/base/nsBase64Encoder.cpp | 67 + netwerk/base/nsBase64Encoder.h | 32 + netwerk/base/nsBaseChannel.cpp | 937 +++++ netwerk/base/nsBaseChannel.h | 300 ++ netwerk/base/nsBaseContentStream.cpp | 137 + netwerk/base/nsBaseContentStream.h | 82 + netwerk/base/nsBufferedStreams.cpp | 832 +++++ netwerk/base/nsBufferedStreams.h | 122 + netwerk/base/nsChannelClassifier.cpp | 696 ++++ netwerk/base/nsChannelClassifier.h | 65 + netwerk/base/nsDNSPrefetch.cpp | 106 + netwerk/base/nsDNSPrefetch.h | 53 + netwerk/base/nsDirectoryIndexStream.cpp | 359 ++ netwerk/base/nsDirectoryIndexStream.h | 49 + netwerk/base/nsDownloader.cpp | 114 + netwerk/base/nsDownloader.h | 40 + netwerk/base/nsFileStreams.cpp | 1153 +++++++ netwerk/base/nsFileStreams.h | 333 ++ netwerk/base/nsIApplicationCache.idl | 205 ++ netwerk/base/nsIApplicationCacheChannel.idl | 57 + netwerk/base/nsIApplicationCacheContainer.idl | 19 + netwerk/base/nsIApplicationCacheService.idl | 110 + netwerk/base/nsIArrayBufferInputStream.idl | 26 + netwerk/base/nsIAsyncStreamCopier.idl | 64 + netwerk/base/nsIAsyncStreamCopier2.idl | 59 + netwerk/base/nsIAsyncVerifyRedirectCallback.idl | 19 + netwerk/base/nsIAuthInformation.idl | 118 + netwerk/base/nsIAuthModule.idl | 145 + netwerk/base/nsIAuthPrompt.idl | 80 + netwerk/base/nsIAuthPrompt2.idl | 103 + netwerk/base/nsIAuthPromptAdapterFactory.idl | 22 + netwerk/base/nsIAuthPromptCallback.idl | 44 + netwerk/base/nsIAuthPromptProvider.idl | 34 + netwerk/base/nsIBackgroundFileSaver.idl | 182 + netwerk/base/nsIBrowserSearchService.idl | 530 +++ netwerk/base/nsIBufferedStreams.idl | 37 + netwerk/base/nsIByteRangeRequest.idl | 27 + netwerk/base/nsICacheInfoChannel.idl | 84 + netwerk/base/nsICachingChannel.idl | 129 + netwerk/base/nsICancelable.idl | 24 + netwerk/base/nsICaptivePortalService.idl | 59 + netwerk/base/nsIChannel.idl | 357 ++ netwerk/base/nsIChannelEventSink.idl | 101 + .../nsIChannelWithDivertableParentListener.idl | 46 + netwerk/base/nsIChildChannel.idl | 35 + netwerk/base/nsIClassOfService.idl | 47 + netwerk/base/nsIContentSniffer.idl | 35 + netwerk/base/nsICryptoFIPSInfo.idl | 15 + netwerk/base/nsICryptoHMAC.idl | 108 + netwerk/base/nsICryptoHash.idl | 105 + netwerk/base/nsIDashboard.idl | 54 + netwerk/base/nsIDashboardEventNotifier.idl | 23 + netwerk/base/nsIDeprecationWarner.idl | 23 + netwerk/base/nsIDivertableChannel.idl | 78 + netwerk/base/nsIDownloader.idl | 51 + netwerk/base/nsIEncodedChannel.idl | 55 + netwerk/base/nsIExternalProtocolHandler.idl | 17 + netwerk/base/nsIFileStreams.idl | 237 ++ netwerk/base/nsIFileURL.idl | 29 + netwerk/base/nsIForcePendingChannel.idl | 22 + netwerk/base/nsIFormPOSTActionChannel.idl | 17 + netwerk/base/nsIHttpAuthenticatorCallback.idl | 31 + netwerk/base/nsIHttpPushListener.idl | 36 + netwerk/base/nsIIOService.idl | 218 ++ netwerk/base/nsIIOService2.idl | 82 + netwerk/base/nsIIncrementalDownload.idl | 109 + netwerk/base/nsIIncrementalStreamLoader.idl | 100 + netwerk/base/nsIInputStreamChannel.idl | 64 + netwerk/base/nsIInputStreamPump.idl | 73 + netwerk/base/nsILoadContextInfo.idl | 89 + netwerk/base/nsILoadGroup.idl | 104 + netwerk/base/nsILoadGroupChild.idl | 42 + netwerk/base/nsILoadInfo.idl | 732 ++++ netwerk/base/nsIMIMEInputStream.idl | 47 + netwerk/base/nsIMultiPartChannel.idl | 35 + netwerk/base/nsINSSErrorsService.idl | 69 + netwerk/base/nsINestedURI.idl | 46 + netwerk/base/nsINetAddr.idl | 88 + netwerk/base/nsINetUtil.idl | 230 ++ netwerk/base/nsINetworkInfoService.idl | 57 + netwerk/base/nsINetworkInterceptController.idl | 144 + netwerk/base/nsINetworkLinkService.idl | 108 + netwerk/base/nsINetworkPredictor.idl | 165 + netwerk/base/nsINetworkPredictorVerifier.idl | 40 + netwerk/base/nsINetworkProperties.idl | 18 + netwerk/base/nsINullChannel.idl | 16 + netwerk/base/nsIOService.cpp | 1880 ++++++++++ netwerk/base/nsIOService.h | 203 ++ netwerk/base/nsIParentChannel.idl | 41 + netwerk/base/nsIParentRedirectingChannel.idl | 46 + netwerk/base/nsIPermission.idl | 77 + netwerk/base/nsIPermissionManager.idl | 271 ++ netwerk/base/nsIPrivateBrowsingChannel.idl | 58 + netwerk/base/nsIProgressEventSink.idl | 80 + netwerk/base/nsIPrompt.idl | 97 + netwerk/base/nsIProtocolHandler.idl | 321 ++ netwerk/base/nsIProtocolProxyCallback.idl | 42 + netwerk/base/nsIProtocolProxyFilter.idl | 74 + netwerk/base/nsIProtocolProxyService.idl | 281 ++ netwerk/base/nsIProtocolProxyService2.idl | 30 + netwerk/base/nsIProxiedChannel.idl | 27 + netwerk/base/nsIProxiedProtocolHandler.idl | 55 + netwerk/base/nsIProxyInfo.idl | 89 + netwerk/base/nsIRandomGenerator.idl | 24 + netwerk/base/nsIRedirectChannelRegistrar.idl | 72 + netwerk/base/nsIRedirectResultListener.idl | 22 + netwerk/base/nsIRequest.idl | 217 ++ netwerk/base/nsIRequestContext.idl | 95 + netwerk/base/nsIRequestObserver.idl | 41 + netwerk/base/nsIRequestObserverProxy.idl | 31 + netwerk/base/nsIResumableChannel.idl | 39 + netwerk/base/nsISecCheckWrapChannel.idl | 24 + netwerk/base/nsISecureBrowserUI.idl | 24 + netwerk/base/nsISecurityEventSink.idl | 26 + netwerk/base/nsISecurityInfoProvider.idl | 21 + netwerk/base/nsISensitiveInfoHiddenURI.idl | 17 + netwerk/base/nsISerializationHelper.idl | 29 + netwerk/base/nsIServerSocket.idl | 237 ++ netwerk/base/nsISimpleStreamListener.idl | 25 + netwerk/base/nsISocketFilter.idl | 53 + netwerk/base/nsISocketTransport.idl | 256 ++ netwerk/base/nsISocketTransportService.idl | 135 + netwerk/base/nsISpeculativeConnect.idl | 77 + netwerk/base/nsIStandardURL.idl | 80 + netwerk/base/nsIStreamListener.idl | 40 + netwerk/base/nsIStreamListenerTee.idl | 50 + netwerk/base/nsIStreamLoader.idl | 77 + netwerk/base/nsIStreamTransportService.idl | 68 + netwerk/base/nsIStreamingProtocolController.idl | 186 + netwerk/base/nsIStreamingProtocolService.idl | 35 + netwerk/base/nsISyncStreamListener.idl | 19 + netwerk/base/nsISystemProxySettings.idl | 42 + netwerk/base/nsITLSServerSocket.idl | 201 ++ netwerk/base/nsIThreadRetargetableRequest.idl | 35 + .../base/nsIThreadRetargetableStreamListener.idl | 33 + netwerk/base/nsIThrottledInputChannel.idl | 80 + netwerk/base/nsITimedChannel.idl | 80 + netwerk/base/nsITraceableChannel.idl | 34 + netwerk/base/nsITransport.idl | 163 + netwerk/base/nsIUDPSocket.idl | 353 ++ netwerk/base/nsIURI.idl | 290 ++ netwerk/base/nsIURIClassifier.idl | 65 + netwerk/base/nsIURIWithBlobImpl.idl | 20 + netwerk/base/nsIURIWithPrincipal.idl | 27 + netwerk/base/nsIURIWithQuery.idl | 30 + netwerk/base/nsIURL.idl | 122 + netwerk/base/nsIURLParser.idl | 98 + netwerk/base/nsIUnicharStreamLoader.idl | 88 + netwerk/base/nsIUploadChannel.idl | 56 + netwerk/base/nsIUploadChannel2.idl | 67 + netwerk/base/nsIncrementalDownload.cpp | 936 +++++ netwerk/base/nsIncrementalStreamLoader.cpp | 214 ++ netwerk/base/nsIncrementalStreamLoader.h | 54 + netwerk/base/nsInputStreamChannel.cpp | 112 + netwerk/base/nsInputStreamChannel.h | 47 + netwerk/base/nsInputStreamPump.cpp | 766 +++++ netwerk/base/nsInputStreamPump.h | 105 + netwerk/base/nsLoadGroup.cpp | 1087 ++++++ netwerk/base/nsLoadGroup.h | 105 + netwerk/base/nsMIMEInputStream.cpp | 390 +++ netwerk/base/nsMIMEInputStream.h | 29 + netwerk/base/nsMediaFragmentURIParser.cpp | 390 +++ netwerk/base/nsMediaFragmentURIParser.h | 106 + netwerk/base/nsNetAddr.cpp | 152 + netwerk/base/nsNetAddr.h | 31 + netwerk/base/nsNetSegmentUtils.h | 23 + netwerk/base/nsNetUtil.cpp | 2459 +++++++++++++ netwerk/base/nsNetUtil.h | 1000 ++++++ netwerk/base/nsNetUtilInlines.h | 395 +++ netwerk/base/nsNetworkInfoService.cpp | 113 + netwerk/base/nsNetworkInfoService.h | 40 + netwerk/base/nsPACMan.cpp | 769 +++++ netwerk/base/nsPACMan.h | 248 ++ netwerk/base/nsPILoadGroupInternal.idl | 28 + netwerk/base/nsPISocketTransportService.idl | 61 + netwerk/base/nsPreloadedStream.cpp | 153 + netwerk/base/nsPreloadedStream.h | 52 + netwerk/base/nsProtocolProxyService.cpp | 2146 ++++++++++++ netwerk/base/nsProtocolProxyService.h | 414 +++ netwerk/base/nsProxyInfo.cpp | 126 + netwerk/base/nsProxyInfo.h | 82 + netwerk/base/nsReadLine.h | 134 + netwerk/base/nsRequestObserverProxy.cpp | 195 ++ netwerk/base/nsRequestObserverProxy.h | 58 + netwerk/base/nsSecCheckWrapChannel.cpp | 196 ++ netwerk/base/nsSecCheckWrapChannel.h | 106 + netwerk/base/nsSerializationHelper.cpp | 73 + netwerk/base/nsSerializationHelper.h | 38 + netwerk/base/nsServerSocket.cpp | 560 +++ netwerk/base/nsServerSocket.h | 67 + netwerk/base/nsSimpleNestedURI.cpp | 190 + netwerk/base/nsSimpleNestedURI.h | 75 + netwerk/base/nsSimpleStreamListener.cpp | 83 + netwerk/base/nsSimpleStreamListener.h | 36 + netwerk/base/nsSimpleURI.cpp | 847 +++++ netwerk/base/nsSimpleURI.h | 110 + netwerk/base/nsSocketTransport2.cpp | 3245 ++++++++++++++++++ netwerk/base/nsSocketTransport2.h | 471 +++ netwerk/base/nsSocketTransportService2.cpp | 1627 +++++++++ netwerk/base/nsSocketTransportService2.h | 282 ++ netwerk/base/nsStandardURL.cpp | 3619 ++++++++++++++++++++ netwerk/base/nsStandardURL.h | 405 +++ netwerk/base/nsStreamListenerTee.cpp | 142 + netwerk/base/nsStreamListenerTee.h | 43 + netwerk/base/nsStreamListenerWrapper.cpp | 32 + netwerk/base/nsStreamListenerWrapper.h | 43 + netwerk/base/nsStreamLoader.cpp | 169 + netwerk/base/nsStreamLoader.h | 58 + netwerk/base/nsStreamTransportService.cpp | 563 +++ netwerk/base/nsStreamTransportService.h | 48 + netwerk/base/nsSyncStreamListener.cpp | 181 + netwerk/base/nsSyncStreamListener.h | 45 + netwerk/base/nsTemporaryFileInputStream.cpp | 252 ++ netwerk/base/nsTemporaryFileInputStream.h | 64 + netwerk/base/nsTransportUtils.cpp | 142 + netwerk/base/nsTransportUtils.h | 26 + netwerk/base/nsUDPSocket.cpp | 1613 +++++++++ netwerk/base/nsUDPSocket.h | 131 + netwerk/base/nsURIHashKey.h | 61 + netwerk/base/nsURLHelper.cpp | 1168 +++++++ netwerk/base/nsURLHelper.h | 250 ++ netwerk/base/nsURLHelperOSX.cpp | 216 ++ netwerk/base/nsURLHelperUnix.cpp | 112 + netwerk/base/nsURLHelperWin.cpp | 117 + netwerk/base/nsURLParsers.cpp | 702 ++++ netwerk/base/nsURLParsers.h | 124 + netwerk/base/nsUnicharStreamLoader.cpp | 250 ++ netwerk/base/nsUnicharStreamLoader.h | 60 + netwerk/base/rust-url-capi/.gitignore | 2 + netwerk/base/rust-url-capi/Cargo.toml | 19 + netwerk/base/rust-url-capi/src/error_mapping.rs | 68 + netwerk/base/rust-url-capi/src/lib.rs | 477 +++ netwerk/base/rust-url-capi/src/rust-url-capi.h | 45 + netwerk/base/rust-url-capi/src/string_utils.rs | 57 + netwerk/base/rust-url-capi/test/Makefile | 4 + netwerk/base/rust-url-capi/test/test.cpp | 141 + netwerk/base/security-prefs.js | 119 + 304 files changed, 67756 insertions(+) create mode 100644 netwerk/base/ADivertableParentChannel.h 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/CaptivePortalService.cpp create mode 100644 netwerk/base/CaptivePortalService.h create mode 100644 netwerk/base/ChannelDiverterChild.cpp create mode 100644 netwerk/base/ChannelDiverterChild.h create mode 100644 netwerk/base/ChannelDiverterParent.cpp create mode 100644 netwerk/base/ChannelDiverterParent.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/EventTokenBucket.cpp create mode 100644 netwerk/base/EventTokenBucket.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/NetStatistics.h create mode 100644 netwerk/base/NetUtil.jsm create mode 100644 netwerk/base/NetworkActivityMonitor.cpp create mode 100644 netwerk/base/NetworkActivityMonitor.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/ProxyAutoConfig.cpp create mode 100644 netwerk/base/ProxyAutoConfig.h create mode 100644 netwerk/base/RedirectChannelRegistrar.cpp create mode 100644 netwerk/base/RedirectChannelRegistrar.h create mode 100644 netwerk/base/ReferrerPolicy.h create mode 100644 netwerk/base/RequestContextService.cpp create mode 100644 netwerk/base/RequestContextService.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/StreamingProtocolService.cpp create mode 100644 netwerk/base/StreamingProtocolService.h create mode 100644 netwerk/base/TLSServerSocket.cpp create mode 100644 netwerk/base/TLSServerSocket.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/moz.build create mode 100644 netwerk/base/mozIThirdPartyUtil.idl 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/nsChannelClassifier.cpp create mode 100644 netwerk/base/nsChannelClassifier.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/nsIApplicationCache.idl create mode 100644 netwerk/base/nsIApplicationCacheChannel.idl create mode 100644 netwerk/base/nsIApplicationCacheContainer.idl create mode 100644 netwerk/base/nsIApplicationCacheService.idl 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/nsIBrowserSearchService.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/nsIChannelWithDivertableParentListener.idl create mode 100644 netwerk/base/nsIChildChannel.idl create mode 100644 netwerk/base/nsIClassOfService.idl create mode 100644 netwerk/base/nsIContentSniffer.idl create mode 100644 netwerk/base/nsICryptoFIPSInfo.idl create mode 100644 netwerk/base/nsICryptoHMAC.idl create mode 100644 netwerk/base/nsICryptoHash.idl create mode 100644 netwerk/base/nsIDashboard.idl create mode 100644 netwerk/base/nsIDashboardEventNotifier.idl create mode 100644 netwerk/base/nsIDeprecationWarner.idl create mode 100644 netwerk/base/nsIDivertableChannel.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/nsIIOService2.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/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/nsINSSErrorsService.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/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/nsINetworkProperties.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/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/nsISecurityEventSink.idl create mode 100644 netwerk/base/nsISecurityInfoProvider.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/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/nsIStreamingProtocolController.idl create mode 100644 netwerk/base/nsIStreamingProtocolService.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/nsIURIClassifier.idl create mode 100644 netwerk/base/nsIURIWithBlobImpl.idl create mode 100644 netwerk/base/nsIURIWithPrincipal.idl create mode 100644 netwerk/base/nsIURIWithQuery.idl create mode 100644 netwerk/base/nsIURL.idl create mode 100644 netwerk/base/nsIURLParser.idl create mode 100644 netwerk/base/nsIUnicharStreamLoader.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/nsNetUtilInlines.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/nsPILoadGroupInternal.idl 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/nsRequestObserverProxy.cpp create mode 100644 netwerk/base/nsRequestObserverProxy.h create mode 100644 netwerk/base/nsSecCheckWrapChannel.cpp create mode 100644 netwerk/base/nsSecCheckWrapChannel.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/nsTemporaryFileInputStream.cpp create mode 100644 netwerk/base/nsTemporaryFileInputStream.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/nsUnicharStreamLoader.cpp create mode 100644 netwerk/base/nsUnicharStreamLoader.h create mode 100644 netwerk/base/rust-url-capi/.gitignore create mode 100644 netwerk/base/rust-url-capi/Cargo.toml create mode 100644 netwerk/base/rust-url-capi/src/error_mapping.rs create mode 100644 netwerk/base/rust-url-capi/src/lib.rs create mode 100644 netwerk/base/rust-url-capi/src/rust-url-capi.h create mode 100644 netwerk/base/rust-url-capi/src/string_utils.rs create mode 100644 netwerk/base/rust-url-capi/test/Makefile create mode 100644 netwerk/base/rust-url-capi/test/test.cpp create mode 100644 netwerk/base/security-prefs.js (limited to 'netwerk/base') diff --git a/netwerk/base/ADivertableParentChannel.h b/netwerk/base/ADivertableParentChannel.h new file mode 100644 index 000000000..2102d3d83 --- /dev/null +++ b/netwerk/base/ADivertableParentChannel.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _adivertablechannelparent_h_ +#define _adivertablechannelparent_h_ + +#include "nsISupports.h" + +class nsIStreamListener; + +namespace mozilla { +namespace net { + +// To be implemented by a channel's parent actors, e.g. HttpChannelParent +// and FTPChannelParent. Used by ChannelDiverterParent to divert +// nsIStreamListener callbacks from the child process to a new +// listener in the parent process. +class ADivertableParentChannel : public nsISupports +{ +public: + // Called by ChannelDiverterParent::DivertTo(nsIStreamListener*). + // The listener should now be used to received nsIStreamListener callbacks, + // i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been + // passed to AsyncOpen for the channel. A reference to the listener will be + // added and kept until OnStopRequest has completed. + virtual void DivertTo(nsIStreamListener *aListener) = 0; + + // Called to suspend parent channel in ChannelDiverterParent constructor. + virtual nsresult SuspendForDiversion() = 0; + + // While messages are diverted back from the child to the parent calls to + // suspend/resume the channel must also suspend/resume the message diversion. + // These two functions will be called by nsHttpChannel and nsFtpChannel + // Suspend()/Resume() functions. + virtual nsresult SuspendMessageDiversion() = 0; + virtual nsresult ResumeMessageDiversion() = 0; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h new file mode 100644 index 000000000..0b2726c43 --- /dev/null +++ b/netwerk/base/ARefBase.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ARefBase_h +#define mozilla_net_ARefBase_h + +#include "nscore.h" + +namespace mozilla { namespace net { + +// This is an abstract class that can be pointed to by either +// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic +// objects that need to be reference counted - similiar to nsISupports +// but it may or may not be xpcom. + +class ARefBase +{ +public: + ARefBase() {} + virtual ~ARefBase() {} + + NS_IMETHOD_ (MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_ (MozExternalRefCountType) Release() = 0; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp new file mode 100644 index 000000000..d70c45110 --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "ArrayBufferInputStream.h" +#include "nsStreamUtils.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream); + +ArrayBufferInputStream::ArrayBufferInputStream() +: mBufferLength(0) +, mPos(0) +, mClosed(false) +{ +} + +NS_IMETHODIMP +ArrayBufferInputStream::SetData(JS::Handle aBuffer, + uint32_t aByteOffset, + uint32_t aLength, + JSContext* aCx) +{ + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject()); + if (!JS_IsArrayBufferObject(arrayBuffer)) { + return NS_ERROR_FAILURE; + } + + uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer); + uint32_t offset = std::min(buflen, aByteOffset); + mBufferLength = std::min(buflen - offset, aLength); + + mArrayBuffer = mozilla::MakeUnique(mBufferLength); + + JS::AutoCheckCannotGC nogc; + bool isShared; + char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset; + memcpy(&mArrayBuffer[0], src, mBufferLength); + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Close() +{ + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Available(uint64_t* aCount) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + if (mArrayBuffer) { + *aCount = mBufferLength ? mBufferLength - mPos : 0; + } else { + *aCount = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure, + uint32_t aCount, uint32_t *result) +{ + NS_ASSERTION(result, "null ptr"); + NS_ASSERTION(mBufferLength >= mPos, "bad stream state"); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), "stream inited incorrectly"); + + *result = 0; + while (mPos < mBufferLength) { + uint32_t remaining = mBufferLength - mPos; + MOZ_ASSERT(mArrayBuffer); + + uint32_t count = std::min(aCount, remaining); + if (count == 0) { + break; + } + + uint32_t written; + nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, &written); + if (NS_FAILED(rv)) { + // InputStreams do not propagate errors to caller. + return NS_OK; + } + + NS_ASSERTION(written <= count, + "writer should not write more than we asked it to write"); + mPos += written; + *result += written; + aCount -= written; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = true; + return NS_OK; +} diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h new file mode 100644 index 000000000..ddd3e7cef --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArrayBufferInputStream_h +#define ArrayBufferInputStream_h + +#include "nsIArrayBufferInputStream.h" +#include "js/Value.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1" +#define NS_ARRAYBUFFERINPUTSTREAM_CID \ +{ /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \ + 0x3014dde6, \ + 0xaa1c, \ + 0x41db, \ + {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \ +} + +class ArrayBufferInputStream : public nsIArrayBufferInputStream { +public: + ArrayBufferInputStream(); + NS_DECL_ISUPPORTS + NS_DECL_NSIARRAYBUFFERINPUTSTREAM + NS_DECL_NSIINPUTSTREAM + +private: + virtual ~ArrayBufferInputStream() {} + mozilla::UniquePtr mArrayBuffer; + uint32_t mBufferLength; + uint32_t mPos; + bool mClosed; +}; + +#endif // ArrayBufferInputStream_h diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h new file mode 100644 index 000000000..43ab27133 --- /dev/null +++ b/netwerk/base/AutoClose.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_AutoClose_h +#define mozilla_net_AutoClose_h + +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" + +namespace mozilla { namespace net { + +// Like an nsAutoPtr for XPCOM streams (e.g. nsIAsyncInputStream) and other +// refcounted classes that need to have the Close() method called explicitly +// before they are destroyed. +template +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) + { + already_AddRefed other = rhs.forget(); + TakeOverInternal(&other); + } + + void CloseAndRelease() + { + TakeOverInternal(nullptr); + } + +private: + void TakeOverInternal(already_AddRefed *aOther) + { + nsCOMPtr ptr; + { + MutexAutoLock lock(mMutex); + ptr.swap(mPtr); + if (aOther) { + mPtr = *aOther; + } + } + + if (ptr) { + ptr->Close(); + } + } + + void operator=(const AutoClose &) = delete; + AutoClose(const AutoClose &) = delete; + + nsCOMPtr mPtr; + Mutex mMutex; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_AutoClose_h diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp new file mode 100644 index 000000000..e4bc05826 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.cpp @@ -0,0 +1,1273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BackgroundFileSaver.h" + +#include "ScopedNSSTypes.h" +#include "mozilla/Casting.h" +#include "mozilla/Logging.h" +#include "mozilla/Telemetry.h" +#include "nsCOMArray.h" +#include "nsIAsyncInputStream.h" +#include "nsIFile.h" +#include "nsIMutableArray.h" +#include "nsIPipe.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsIX509CertList.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "pk11pub.h" +#include "secoidt.h" + +#ifdef XP_WIN +#include +#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) + : 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() +: mControlThread(nullptr) +, mWorkerThread(nullptr) +, mPipeOutputStream(nullptr) +, mPipeInputStream(nullptr) +, mObserver(nullptr) +, mLock("BackgroundFileSaver.mLock") +, mWorkerThreadAttentionRequested(false) +, mFinishRequested(false) +, mComplete(false) +, mStatus(NS_OK) +, mAppend(false) +, mInitialTarget(nullptr) +, mInitialTargetKeepPartial(false) +, mRenamedTarget(nullptr) +, mRenamedTargetKeepPartial(false) +, mAsyncCopyContext(nullptr) +, mSha256Enabled(false) +, mSignatureInfoEnabled(false) +, mActualTarget(nullptr) +, mActualTargetKeepPartial(false) +, mDigestContext(nullptr) +{ + LOG(("Created BackgroundFileSaver [this = %p]", this)); +} + +BackgroundFileSaver::~BackgroundFileSaver() +{ + LOG(("Destroying BackgroundFileSaver [this = %p]", this)); + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +void +BackgroundFileSaver::destructorSafeDestroyNSSReference() +{ + mDigestContext = nullptr; +} + +void +BackgroundFileSaver::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +// Called on the control thread. +nsresult +BackgroundFileSaver::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + nsresult rv; + + rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream), + getter_AddRefs(mPipeOutputStream), true, true, 0, + HasInfiniteBuffer() ? UINT32_MAX : 0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetCurrentThread(getter_AddRefs(mControlThread)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewThread(getter_AddRefs(mWorkerThread)); + NS_ENSURE_SUCCESS(rv, rv); + + sThreadCount++; + if (sThreadCount > sTelemetryMaxThreadCount) { + sTelemetryMaxThreadCount = sThreadCount; + } + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver **aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + *aObserver = mObserver; + NS_IF_ADDREF(*aObserver); + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver *aObserver) +{ + mObserver = aObserver; + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::EnableAppend() +{ + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + MutexAutoLock lock(mLock); + mAppend = true; + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetTarget(nsIFile *aTarget, bool aKeepPartial) +{ + NS_ENSURE_ARG(aTarget); + { + MutexAutoLock lock(mLock); + if (!mInitialTarget) { + aTarget->Clone(getter_AddRefs(mInitialTarget)); + mInitialTargetKeepPartial = aKeepPartial; + } else { + aTarget->Clone(getter_AddRefs(mRenamedTarget)); + mRenamedTargetKeepPartial = aKeepPartial; + } + } + + // After the worker thread wakes up because attention is requested, it will + // rename or create the target file as requested, and start copying data. + return GetWorkerThreadAttention(true); +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::Finish(nsresult aStatus) +{ + nsresult rv; + + // This will cause the NS_AsyncCopy operation, if it's in progress, to consume + // all the data that is still in the pipe, and then finish. + rv = mPipeOutputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + // Ensure that, when we get attention from the worker thread, if no pending + // rename operation is waiting, the operation will complete. + { + MutexAutoLock lock(mLock); + mFinishRequested = true; + if (NS_SUCCEEDED(mStatus)) { + mStatus = aStatus; + } + } + + // After the worker thread wakes up because attention is requested, it will + // process the completion conditions, detect that completion is requested, and + // notify the main thread of the completion. If this function was called with + // a success code, we wait for the copy to finish before processing the + // completion conditions, otherwise we interrupt the copy immediately. + return GetWorkerThreadAttention(NS_FAILED(aStatus)); +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSha256() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable sha256 or initialize NSS off the main thread"); + // Ensure Personal Security Manager is initialized. This is required for + // PK11_* operations to work. + nsresult rv; + nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + mSha256Enabled = true; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSha256Hash(nsACString& aHash) +{ + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread"); + // We acquire a lock because mSha256 is written on the worker thread. + MutexAutoLock lock(mLock); + if (mSha256.IsEmpty()) { + return NS_ERROR_NOT_AVAILABLE; + } + aHash = mSha256; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSignatureInfo() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable signature extraction off the main thread"); + // Ensure Personal Security Manager is initialized. + nsresult rv; + nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + mSignatureInfoEnabled = true; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo) +{ + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread"); + // We acquire a lock because mSignatureInfo is written on the worker thread. + MutexAutoLock lock(mLock); + if (!mComplete || !mSignatureInfoEnabled) { + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID); + for (int i = 0; i < mSignatureInfo.Count(); ++i) { + sigArray->AppendElement(mSignatureInfo[i], false); + } + *aSignatureInfo = sigArray; + NS_IF_ADDREF(*aSignatureInfo); + return NS_OK; +} + +// Called on the control thread. +nsresult +BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy) +{ + nsresult rv; + + MutexAutoLock lock(mLock); + + // We only require attention one time. If this function is called two times + // before the worker thread wakes up, and the first has aShouldInterruptCopy + // false and the second true, we won't forcibly interrupt the copy from the + // control thread. However, that never happens, because calling Finish with a + // success code is the only case that may result in aShouldInterruptCopy being + // false. In that case, we won't call this function again, because consumers + // should not invoke other methods on the control thread after calling Finish. + // And in any case, Finish already closes one end of the pipe, causing the + // copy to finish properly on its own. + if (mWorkerThreadAttentionRequested) { + return NS_OK; + } + + if (!mAsyncCopyContext) { + // Copy is not in progress, post an event to handle the change manually. + rv = mWorkerThread->Dispatch(NewRunnableMethod(this, + &BackgroundFileSaver::ProcessAttention), + NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aShouldInterruptCopy) { + // Interrupt the copy. The copy will be resumed, if needed, by the + // ProcessAttention function, invoked by the AsyncCopyCallback function. + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + } + + // Indicate that attention has been requested successfully, there is no need + // to post another event until the worker thread processes the current one. + mWorkerThreadAttentionRequested = true; + + return NS_OK; +} + +// Called on the worker thread. +// static +void +BackgroundFileSaver::AsyncCopyCallback(void *aClosure, nsresult aStatus) +{ + BackgroundFileSaver *self = (BackgroundFileSaver *)aClosure; + { + MutexAutoLock lock(self->mLock); + + // Now that the copy was interrupted or terminated, any notification from + // the control thread requires an event to be posted to the worker thread. + self->mAsyncCopyContext = nullptr; + + // When detecting failures, ignore the status code we use to interrupt. + if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT && + NS_SUCCEEDED(self->mStatus)) { + self->mStatus = aStatus; + } + } + + (void)self->ProcessAttention(); + + // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object + // alive even if other references disappeared. At this point, we've finished + // using the object and can safely release our reference. + NS_RELEASE(self); +} + +// Called on the worker thread. +nsresult +BackgroundFileSaver::ProcessAttention() +{ + nsresult rv; + + // This function is called whenever the attention of the worker thread has + // been requested. This may happen in these cases: + // * We are about to start the copy for the first time. In this case, we are + // called from an event posted on the worker thread from the control thread + // by GetWorkerThreadAttention, and mAsyncCopyContext is null. + // * We have interrupted the copy for some reason. In this case, we are + // called by AsyncCopyCallback, and mAsyncCopyContext is null. + // * We are currently executing ProcessStateChange, and attention is requested + // by the control thread, for example because SetTarget or Finish have been + // called. In this case, we are called from from an event posted through + // GetWorkerThreadAttention. While mAsyncCopyContext was always null when + // the event was posted, at this point mAsyncCopyContext may not be null + // anymore, because ProcessStateChange may have started the copy before the + // event that called this function was processed on the worker thread. + // If mAsyncCopyContext is not null, we interrupt the copy and re-enter + // through AsyncCopyCallback. This allows us to check if, for instance, we + // should rename the target file. We will then restart the copy if needed. + if (mAsyncCopyContext) { + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + return NS_OK; + } + // Use the current shared state to determine the next operation to execute. + rv = ProcessStateChange(); + if (NS_FAILED(rv)) { + // If something failed while processing, terminate the operation now. + { + MutexAutoLock lock(mLock); + + if (NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + } + // Ensure we notify completion now that the operation failed. + CheckCompletion(); + } + + return NS_OK; +} + +// Called on the worker thread. +nsresult +BackgroundFileSaver::ProcessStateChange() +{ + nsresult rv; + + // We might have been notified because the operation is complete, verify. + if (CheckCompletion()) { + return NS_OK; + } + + // Get a copy of the current shared state for the worker thread. + nsCOMPtr 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); + } + + // Now we can update the actual target file name. + mActualTarget = renamedTarget; + mActualTargetKeepPartial = renamedTargetKeepPartial; + } + } + + // Notify if the target file name actually changed. + if (!equalToCurrent) { + // We must clone the nsIFile instance because mActualTarget is not + // immutable, it may change if the target is renamed later. + nsCOMPtr 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 = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isContinuation) { + // The pending rename operation might be the last task before finishing. We + // may return here only if we have already created the target file. + if (CheckCompletion()) { + return NS_OK; + } + + // Even if the operation did not complete, the pipe input stream may be + // empty and may have been closed already. We detect this case using the + // Available property, because it never returns an error if there is more + // data to be consumed. If the pipe input stream is closed, we just exit + // and wait for more calls like SetTarget or Finish to be invoked on the + // control thread. However, we still truncate the file or create the + // initial digest context if we are expected to do that. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_FAILED(rv)) { + return NS_OK; + } + } + + // Create the digest context if requested and NSS hasn't been shut down. + if (sha256Enabled && !mDigestContext) { + nsNSSShutDownPreventionLock lock; + if (!isAlreadyShutDown()) { + mDigestContext = UniquePK11Context( + PK11_CreateDigestContext(SEC_OID_SHA256)); + NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY); + } + } + + // When we are requested to append to an existing file, we should read the + // existing data and ensure we include it as part of the final hash. + if (mDigestContext && append && !isContinuation) { + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), + mActualTarget, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (rv != NS_ERROR_FILE_NOT_FOUND) { + NS_ENSURE_SUCCESS(rv, rv); + + char buffer[BUFFERED_IO_SIZE]; + while (true) { + uint32_t count; + rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count == 0) { + // We reached the end of the file. + break; + } + + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = MapSECStatus( + PK11_DigestOp(mDigestContext.get(), + BitwiseCast(buffer), + count)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = inputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // We will append to the initial target file only if it was requested by the + // caller, but we'll always append on subsequent accesses to the target file. + int32_t creationIoFlags; + if (isContinuation) { + creationIoFlags = PR_APPEND; + } else { + creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; + } + + // Create the target file, or append to it if we already started writing it. + // The 0600 permissions are used while the file is being downloaded, and for + // interrupted downloads. Those may be located in the system temporary + // directory, as well as the target directory, and generally have a ".part" + // extension. Those part files should never be group or world-writable even + // if the umask allows it. + nsCOMPtr outputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), + mActualTarget, + PR_WRONLY | creationIoFlags, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE); + if (!outputStream) { + return NS_ERROR_FAILURE; + } + + // Wrap the output stream so that it feeds the digest context if needed. + if (mDigestContext) { + // No need to acquire the NSS lock here, DigestOutputStream must acquire it + // in any case before each asynchronous write. Constructing the + // DigestOutputStream cannot fail. Passing mDigestContext to + // DigestOutputStream is safe, because BackgroundFileSaver always outlives + // the outputStream. BackgroundFileSaver is reference-counted before the + // call to AsyncCopy, and mDigestContext is never destroyed before + // AsyncCopyCallback. + outputStream = new DigestOutputStream(outputStream, mDigestContext.get()); + } + + // Start copying our input to the target file. No errors can be raised past + // this point if the copy starts, since they should be handled by the thread. + { + MutexAutoLock lock(mLock); + + rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread, + NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, + this, false, true, getter_AddRefs(mAsyncCopyContext), + GetProgressCallback()); + if (NS_FAILED(rv)) { + NS_WARNING("NS_AsyncCopy failed."); + mAsyncCopyContext = nullptr; + return rv; + } + } + + // If the operation succeeded, we must ensure that we keep this object alive + // for the entire duration of the copy, since only the raw pointer will be + // provided as the argument of the AsyncCopyCallback function. We can add the + // reference now, after NS_AsyncCopy returned, because it always starts + // processing asynchronously, and there is no risk that the callback is + // invoked before we reach this point. If the operation failed instead, then + // AsyncCopyCallback will never be called. + NS_ADDREF_THIS(); + + return NS_OK; +} + +// Called on the worker thread. +bool +BackgroundFileSaver::CheckCompletion() +{ + nsresult rv; + + MOZ_ASSERT(!mAsyncCopyContext, + "Should not be copying when checking completion conditions."); + + bool failed = true; + { + MutexAutoLock lock(mLock); + + if (mComplete) { + return true; + } + + // If an error occurred, we don't need to do the checks in this code block, + // and the operation can be completed immediately with a failure code. + if (NS_SUCCEEDED(mStatus)) { + failed = false; + + // We did not incur in an error, so we must determine if we can stop now. + // If the Finish method has not been called, we can just continue now. + if (!mFinishRequested) { + return false; + } + + // We can only stop when all the operations requested by the control + // thread have been processed. First, we check whether we have processed + // the first SetTarget call, if any. Then, we check whether we have + // processed any rename requested by subsequent SetTarget calls. + if ((mInitialTarget && !mActualTarget) || + (mRenamedTarget && mRenamedTarget != mActualTarget)) { + return false; + } + + // If we still have data to write to the output file, allow the copy + // operation to resume. The Available getter may return an error if one + // of the pipe's streams has been already closed. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available != 0) { + return false; + } + } + + mComplete = true; + } + + // Ensure we notify completion now that the operation finished. + // Do a best-effort attempt to remove the file if required. + if (failed && mActualTarget && !mActualTargetKeepPartial) { + (void)mActualTarget->Remove(false); + } + + // Finish computing the hash + if (!failed && mDigestContext) { + nsNSSShutDownPreventionLock lock; + if (!isAlreadyShutDown()) { + Digest d; + rv = d.End(SEC_OID_SHA256, mDigestContext); + if (NS_SUCCEEDED(rv)) { + MutexAutoLock lock(mLock); + mSha256 = + nsDependentCSubstring(BitwiseCast(d.get().data), + d.get().len); + } + } + } + + // Compute the signature of the binary. ExtractSignatureInfo doesn't do + // anything on non-Windows platforms except return an empty nsIArray. + if (!failed && mActualTarget) { + nsString filePath; + mActualTarget->GetTarget(filePath); + nsresult rv = ExtractSignatureInfo(filePath); + if (NS_FAILED(rv)) { + LOG(("Unable to extract signature information [this = %p].", this)); + } else { + LOG(("Signature extraction success! [this = %p]", this)); + } + } + + // Post an event to notify that the operation completed. + if (NS_FAILED(mControlThread->Dispatch(NewRunnableMethod(this, + &BackgroundFileSaver::NotifySaveComplete), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post completion event to the control thread."); + } + + return true; +} + +// Called on the control thread. +nsresult +BackgroundFileSaver::NotifyTargetChange(nsIFile *aTarget) +{ + if (mObserver) { + (void)mObserver->OnTargetChange(this, aTarget); + } + + return NS_OK; +} + +// Called on the control thread. +nsresult +BackgroundFileSaver::NotifySaveComplete() +{ + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + nsresult status; + { + MutexAutoLock lock(mLock); + status = mStatus; + } + + if (mObserver) { + (void)mObserver->OnSaveComplete(this, status); + } + + // At this point, the worker thread will not process any more events, and we + // can shut it down. Shutting down a thread may re-enter the event loop on + // this thread. This is not a problem in this case, since this function is + // called by a top-level event itself, and we have already invoked the + // completion observer callback. Re-entering the loop can only delay the + // final release and destruction of this saver object, since we are keeping a + // reference to it through the event object. + mWorkerThread->Shutdown(); + + sThreadCount--; + + // When there are no more active downloads, we consider the download session + // finished. We record the maximum number of concurrent downloads reached + // during the session in a telemetry histogram, and we reset the maximum + // thread counter for the next download session + if (sThreadCount == 0) { + Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT, + sTelemetryMaxThreadCount); + sTelemetryMaxThreadCount = 0; + } + + return NS_OK; +} + +nsresult +BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) +{ + MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread"); + + nsNSSShutDownPreventionLock nssLock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + { + MutexAutoLock lock(mLock); + if (!mSignatureInfoEnabled) { + return NS_OK; + } + } + nsresult rv; + nsCOMPtr certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef XP_WIN + // Setup the file to check. + WINTRUST_FILE_INFO fileToCheck = {0}; + fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileToCheck.pcwszFilePath = filePath.Data(); + fileToCheck.hFile = nullptr; + fileToCheck.pgKnownSubject = nullptr; + + // We want to check it is signed and trusted. + WINTRUST_DATA trustData = {0}; + trustData.cbStruct = sizeof(trustData); + trustData.pPolicyCallbackData = nullptr; + trustData.pSIPClientData = nullptr; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.hWVTStateData = nullptr; + trustData.pwszURLReference = nullptr; + // Disallow revocation checks over the network + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + // no UI + trustData.dwUIContext = 0; + trustData.pFile = &fileToCheck; + + // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate + // chains up to a trusted root CA and has appropriate permissions to sign + // code. + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + // Check if the file is signed by something that is trusted. If the file is + // not signed, this is a no-op. + LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); + CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr; + // According to the Windows documentation, we should check against 0 instead + // of ERROR_SUCCESS, which is an HRESULT. + if (ret == 0) { + cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData); + } + if (cryptoProviderData) { + // Lock because signature information is read on the main thread. + MutexAutoLock lock(mLock); + LOG(("Downloaded trusted and signed file [this = %p].", this)); + // A binary may have multiple signers. Each signer may have multiple certs + // in the chain. + for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) { + const CERT_CHAIN_CONTEXT* certChainContext = + cryptoProviderData->pasSigners[i].pChainContext; + if (!certChainContext) { + break; + } + for (DWORD j = 0; j < certChainContext->cChain; ++j) { + const CERT_SIMPLE_CHAIN* certSimpleChain = + certChainContext->rgpChain[j]; + if (!certSimpleChain) { + break; + } + nsCOMPtr nssCertList = + do_CreateInstance(NS_X509CERTLIST_CONTRACTID); + if (!nssCertList) { + break; + } + bool extractionSuccess = true; + for (DWORD k = 0; k < certSimpleChain->cElement; ++k) { + CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k]; + if (certChainElement->pCertContext->dwCertEncodingType != + X509_ASN_ENCODING) { + continue; + } + nsCOMPtr nssCert = nullptr; + rv = certDB->ConstructX509( + reinterpret_cast( + certChainElement->pCertContext->pbCertEncoded), + certChainElement->pCertContext->cbCertEncoded, + getter_AddRefs(nssCert)); + if (!nssCert) { + extractionSuccess = false; + LOG(("Couldn't create NSS cert [this = %p]", this)); + break; + } + nssCertList->AddCert(nssCert); + nsString subjectName; + nssCert->GetSubjectName(subjectName); + LOG(("Adding cert %s [this = %p]", + NS_ConvertUTF16toUTF8(subjectName).get(), this)); + } + if (extractionSuccess) { + mSignatureInfo.AppendObject(nssCertList); + } + } + } + // Free the provider data if cryptoProviderData is not null. + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(nullptr, &policyGUID, &trustData); + } else { + LOG(("Downloaded unsigned or untrusted file [this = %p].", this)); + } +#endif + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, + nsIBackgroundFileSaver, + nsIOutputStream, + nsIAsyncOutputStream, + nsIOutputStreamCallback) + +BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream() +: BackgroundFileSaver() +, mAsyncWaitCallback(nullptr) +{ +} + +BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream() +{ +} + +bool +BackgroundFileSaverOutputStream::HasInfiniteBuffer() +{ + return false; +} + +nsAsyncCopyProgressFun +BackgroundFileSaverOutputStream::GetProgressCallback() +{ + return nullptr; +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Close() +{ + return mPipeOutputStream->Close(); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Flush() +{ + return mPipeOutputStream->Flush(); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Write(const char *aBuf, uint32_t aCount, + uint32_t *_retval) +{ + return mPipeOutputStream->Write(aBuf, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream *aFromStream, + uint32_t aCount, uint32_t *_retval) +{ + return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader, + void *aClosure, uint32_t aCount, + uint32_t *_retval) +{ + return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::IsNonBlocking(bool *_retval) +{ + return mPipeOutputStream->IsNonBlocking(_retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) +{ + return mPipeOutputStream->CloseWithStatus(reason); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget *aEventTarget) +{ + NS_ENSURE_STATE(!mAsyncWaitCallback); + + mAsyncWaitCallback = aCallback; + + return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount, + aEventTarget); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::OnOutputStreamReady( + nsIAsyncOutputStream *aStream) +{ + NS_ENSURE_STATE(mAsyncWaitCallback); + + nsCOMPtr asyncWaitCallback = nullptr; + asyncWaitCallback.swap(mAsyncWaitCallback); + + return asyncWaitCallback->OnOutputStreamReady(this); +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener + +NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, + nsIBackgroundFileSaver, + nsIRequestObserver, + nsIStreamListener) + +BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener() +: BackgroundFileSaver() +, mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock") +, mReceivedTooMuchData(false) +, mRequest(nullptr) +, mRequestSuspended(false) +{ +} + +BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener() +{ +} + +bool +BackgroundFileSaverStreamListener::HasInfiniteBuffer() +{ + return true; +} + +nsAsyncCopyProgressFun +BackgroundFileSaverStreamListener::GetProgressCallback() +{ + return AsyncCopyProgressCallback; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + NS_ENSURE_ARG(aRequest); + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + // If an error occurred, cancel the operation immediately. On success, wait + // until the caller has determined whether the file should be renamed. + if (NS_FAILED(aStatusCode)) { + Finish(aStatusCode); + } + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, + uint32_t aCount) +{ + nsresult rv; + + NS_ENSURE_ARG(aRequest); + + // Read the requested data. Since the pipe has an infinite buffer, we don't + // expect any write error to occur here. + uint32_t writeCount; + rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + + // If reading from the input stream fails for any reason, the pipe will return + // a success code, but without reading all the data. Since we should be able + // to read the requested data when OnDataAvailable is called, raise an error. + if (writeCount < aCount) { + NS_WARNING("Reading from the input stream should not have failed."); + return NS_ERROR_UNEXPECTED; + } + + bool stateChanged = false; + { + MutexAutoLock lock(mSuspensionLock); + + if (!mReceivedTooMuchData) { + uint64_t available; + nsresult rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) { + mReceivedTooMuchData = true; + mRequest = aRequest; + stateChanged = true; + } + } + } + + if (stateChanged) { + NotifySuspendOrResume(); + } + + return NS_OK; +} + +// Called on the worker thread. +// static +void +BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void *aClosure, + uint32_t aCount) +{ + BackgroundFileSaverStreamListener *self = + (BackgroundFileSaverStreamListener *)aClosure; + + // Wait if the control thread is in the process of suspending or resuming. + MutexAutoLock lock(self->mSuspensionLock); + + // This function is called when some bytes are consumed by NS_AsyncCopy. Each + // time this happens, verify if a suspended request should be resumed, because + // we have now consumed enough data. + if (self->mReceivedTooMuchData) { + uint64_t available; + nsresult rv = self->mPipeInputStream->Available(&available); + if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) { + self->mReceivedTooMuchData = false; + + // Post an event to verify if the request should be resumed. + if (NS_FAILED(self->mControlThread->Dispatch(NewRunnableMethod(self, + &BackgroundFileSaverStreamListener::NotifySuspendOrResume), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post resume event to the control thread."); + } + } + } +} + +// Called on the control thread. +nsresult +BackgroundFileSaverStreamListener::NotifySuspendOrResume() +{ + // Prevent the worker thread from changing state while processing. + MutexAutoLock lock(mSuspensionLock); + + if (mReceivedTooMuchData) { + if (!mRequestSuspended) { + // Try to suspend the request. If this fails, don't try to resume later. + if (NS_SUCCEEDED(mRequest->Suspend())) { + mRequestSuspended = true; + } else { + NS_WARNING("Unable to suspend the request."); + } + } + } else { + if (mRequestSuspended) { + // Resume the request only if we succeeded in suspending it. + if (NS_SUCCEEDED(mRequest->Resume())) { + mRequestSuspended = false; + } else { + NS_WARNING("Unable to resume the request."); + } + } + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// DigestOutputStream +NS_IMPL_ISUPPORTS(DigestOutputStream, + nsIOutputStream) + +DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream, + PK11Context* aContext) : + mOutputStream(aStream) + , mDigestContext(aContext) +{ + MOZ_ASSERT(mDigestContext, "Can't have null digest context"); + MOZ_ASSERT(mOutputStream, "Can't have null output stream"); +} + +DigestOutputStream::~DigestOutputStream() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + shutdown(ShutdownCalledFrom::Object); +} + +NS_IMETHODIMP +DigestOutputStream::Close() +{ + return mOutputStream->Close(); +} + +NS_IMETHODIMP +DigestOutputStream::Flush() +{ + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) +{ + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = MapSECStatus( + PK11_DigestOp(mDigestContext, + BitwiseCast(aBuf), + aCount)); + NS_ENSURE_SUCCESS(rv, rv); + + return mOutputStream->Write(aBuf, aCount, retval); +} + +NS_IMETHODIMP +DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, uint32_t* retval) +{ + // Not supported. We could read the stream to a buf, call DigestOp on the + // result, seek back and pass the stream on, but it's not worth it since our + // application (NS_AsyncCopy) doesn't invoke this on the sink. + MOZ_CRASH("DigestOutputStream::WriteFrom not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, + void *aClosure, uint32_t aCount, + uint32_t *retval) +{ + MOZ_CRASH("DigestOutputStream::WriteSegments not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::IsNonBlocking(bool *retval) +{ + return mOutputStream->IsNonBlocking(retval); +} + +#undef LOG_ENABLED + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h new file mode 100644 index 000000000..1fa9268f8 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.h @@ -0,0 +1,418 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file defines two implementations of the nsIBackgroundFileSaver + * interface. See the "test_backgroundfilesaver.js" file for usage examples. + */ + +#ifndef BackgroundFileSaver_h__ +#define BackgroundFileSaver_h__ + +#include "ScopedNSSTypes.h" +#include "mozilla/Mutex.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBackgroundFileSaver.h" +#include "nsIStreamListener.h" +#include "nsNSSShutDown.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +class nsIAsyncInputStream; +class nsIThread; +class nsIX509CertList; + +namespace mozilla { +namespace net { + +class DigestOutputStream; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +class BackgroundFileSaver : public nsIBackgroundFileSaver, + public nsNSSShutDownObject +{ +public: + NS_DECL_NSIBACKGROUNDFILESAVER + + BackgroundFileSaver(); + + /** + * Initializes the pipe and the worker thread on XPCOM construction. + * + * This is called automatically by the XPCOM infrastructure, and if this + * fails, the factory will delete this object without returning a reference. + */ + nsresult Init(); + + /** + * Used by nsNSSShutDownList to manage nsNSSShutDownObjects. + */ + void virtualDestroyNSSReference() override; + + /** + * Number of worker threads that are currently running. + */ + static uint32_t sThreadCount; + + /** + * Maximum number of worker threads reached during the current download session, + * used for telemetry. + * + * When there are no more worker threads running, we consider the download + * session finished, and this counter is reset. + */ + static uint32_t sTelemetryMaxThreadCount; + + +protected: + virtual ~BackgroundFileSaver(); + + /** + * Helper function for managing NSS objects (mDigestContext). + */ + void destructorSafeDestroyNSSReference(); + + /** + * Thread that constructed this object. + */ + nsCOMPtr mControlThread; + + /** + * Thread to which the actual input/output is delegated. + */ + nsCOMPtr mWorkerThread; + + /** + * Stream that receives data from derived classes. The received data will be + * available to the worker thread through mPipeInputStream. This is an + * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. + */ + nsCOMPtr 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; + + /** + * True if the worker thread is already waiting to process a change in state. + */ + bool mWorkerThreadAttentionRequested; + + /** + * True if the operation should finish as soon as possibile. + */ + bool mFinishRequested; + + /** + * True if the operation completed, with either success or failure. + */ + bool mComplete; + + /** + * Holds the current file saver status. This is a success status while the + * object is working correctly, and remains such if the operation completes + * successfully. This becomes an error status when an error occurs on the + * worker thread, or when the operation is canceled. + */ + nsresult mStatus; + + /** + * True if we should append data to the initial target file, instead of + * overwriting it. + */ + bool mAppend; + + /** + * This is set by the first SetTarget call on the control thread, and contains + * the target file name that will be used by the worker thread, as soon as it + * is possible to update mActualTarget and open the file. This is null if no + * target was ever assigned to this object. + */ + nsCOMPtr mInitialTarget; + + /** + * This is set by the first SetTarget call on the control thread, and + * indicates whether mInitialTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mInitialTargetKeepPartial; + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * contains the new target file name to which the worker thread will move the + * target file, as soon as it can be done. This is null if SetTarget was + * called only once, or no target was ever assigned to this object. + * + * The target file can be renamed multiple times, though only the most recent + * rename is guaranteed to be processed by the worker thread. + */ + nsCOMPtr mRenamedTarget; + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * indicates whether mRenamedTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mRenamedTargetKeepPartial; + + /** + * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. + * This is read by both threads but only written by the worker thread. + */ + nsCOMPtr mAsyncCopyContext; + + /** + * The SHA 256 hash in raw bytes of the downloaded file. This is written + * by the worker thread but can be read on the main thread. + */ + nsCString mSha256; + + /** + * Whether or not to compute the hash. Must be set on the main thread before + * setTarget is called. + */ + bool mSha256Enabled; + + /** + * Store the signature info. + */ + nsCOMArray mSignatureInfo; + + /** + * Whether or not to extract the signature. Must be set on the main thread + * before setTarget is called. + */ + bool mSignatureInfoEnabled; + + ////////////////////////////////////////////////////////////////////////////// + //// State handled exclusively by the worker thread + + /** + * Current target file associated to the input and output streams. + */ + nsCOMPtr mActualTarget; + + /** + * Indicates whether mActualTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mActualTargetKeepPartial; + + /** + * Used to calculate the file hash. This keeps state across file renames and + * is lazily initialized in ProcessStateChange. + */ + UniquePK11Context mDigestContext; + + ////////////////////////////////////////////////////////////////////////////// + //// Private methods + + /** + * Called when NS_AsyncCopy completes. + * + * @param aClosure + * Populated with a raw pointer to the BackgroundFileSaver object. + * @param aStatus + * Success or failure status specified when the copy was interrupted. + */ + static void AsyncCopyCallback(void *aClosure, nsresult aStatus); + + /** + * Called on the control thread after state changes, to ensure that the worker + * thread will process the state change appropriately. + * + * @param aShouldInterruptCopy + * If true, the current NS_AsyncCopy, if any, is canceled. + */ + nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); + + /** + * Event called on the worker thread to begin processing a state change. + */ + nsresult ProcessAttention(); + + /** + * Called by ProcessAttention to execute the operations corresponding to the + * state change. If this results in an error, ProcessAttention will force the + * entire operation to be aborted. + */ + nsresult ProcessStateChange(); + + /** + * Returns true if completion conditions are met on the worker thread. The + * first time this happens, posts the completion event to the control thread. + */ + bool CheckCompletion(); + + /** + * Event called on the control thread to indicate that file contents will now + * be saved to the specified file. + */ + nsresult NotifyTargetChange(nsIFile *aTarget); + + /** + * Event called on the control thread to send the final notification. + */ + nsresult NotifySaveComplete(); + + /** + * Verifies the signature of the binary at the specified file path and stores + * the signature data in mSignatureInfo. We extract only X.509 certificates, + * since that is what Google's Safebrowsing protocol specifies. + */ + nsresult ExtractSignatureInfo(const nsAString& filePath); +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +class BackgroundFileSaverOutputStream : public BackgroundFileSaver + , public nsIAsyncOutputStream + , public nsIOutputStreamCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSIOUTPUTSTREAMCALLBACK + + BackgroundFileSaverOutputStream(); + +protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + +private: + ~BackgroundFileSaverOutputStream(); + + /** + * Original callback provided to our AsyncWait wrapper. + */ + nsCOMPtr mAsyncWaitCallback; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener. This class is instantiated by +// nsExternalHelperAppService, DownloadCore.jsm, and possibly others. + +class BackgroundFileSaverStreamListener final : public BackgroundFileSaver + , public nsIStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + BackgroundFileSaverStreamListener(); + +protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + +private: + ~BackgroundFileSaverStreamListener(); + + /** + * Protects the state related to whether the request should be suspended. + */ + mozilla::Mutex mSuspensionLock; + + /** + * Whether we should suspend the request because we received too much data. + */ + bool mReceivedTooMuchData; + + /** + * Request for which we received too much data. This is populated when + * mReceivedTooMuchData becomes true for the first time. + */ + nsCOMPtr mRequest; + + /** + * Whether mRequest is currently suspended. + */ + bool mRequestSuspended; + + /** + * Called while NS_AsyncCopy is copying data. + */ + static void AsyncCopyProgressCallback(void *aClosure, uint32_t aCount); + + /** + * Called on the control thread to suspend or resume the request. + */ + nsresult NotifySuspendOrResume(); +}; + +// A wrapper around nsIOutputStream, so that we can compute hashes on the +// stream without copying and without polluting pristine NSS code with XPCOM +// interfaces. +class DigestOutputStream : public nsNSSShutDownObject, + public nsIOutputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + // Constructor. Neither parameter may be null. The caller owns both. + DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext); + + // We don't own any NSS objects here, so no need to clean up + void virtualDestroyNSSReference() override { } + +private: + ~DigestOutputStream(); + + // Calls to write are passed to this stream. + nsCOMPtr mOutputStream; + // Digest context used to compute the hash, owned by the caller. + PK11Context* mDigestContext; + + // Don't accidentally copy construct. + DigestOutputStream(const DigestOutputStream& d); +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp new file mode 100644 index 000000000..f97fb41d7 --- /dev/null +++ b/netwerk/base/CaptivePortalService.cpp @@ -0,0 +1,366 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" + +static const char16_t kInterfaceName[] = u"captive-portal-inteface"; + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort"; +static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success"; + +static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds + +namespace mozilla { +namespace net { + +static LazyLogModule gCaptivePortalLog("CaptivePortalService"); +#undef LOG +#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver, + nsISupportsWeakReference, nsITimerCallback, + nsICaptivePortalCallback) + +CaptivePortalService::CaptivePortalService() + : mState(UNKNOWN) + , mStarted(false) + , mInitialized(false) + , mRequestInProgress(false) + , mEverBeenCaptive(false) + , mDelay(kDefaultInterval) + , mSlackCount(0) + , mMinInterval(kDefaultInterval) + , mMaxInterval(25*kDefaultInterval) + , mBackoffFactor(5.0) +{ + mLastChecked = TimeStamp::Now(); +} + +CaptivePortalService::~CaptivePortalService() +{ + LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n", + XRE_GetProcessType() == GeckoProcessType_Default)); +} + +nsresult +CaptivePortalService::PerformCheck() +{ + LOG(("CaptivePortalService::PerformCheck mRequestInProgress:%d mInitialized:%d mStarted:%d\n", + mRequestInProgress, mInitialized, mStarted)); + // Don't issue another request if last one didn't complete + if (mRequestInProgress || !mInitialized || !mStarted) { + return NS_OK; + } + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + nsresult rv; + if (!mCaptivePortalDetector) { + mCaptivePortalDetector = + do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv); + if (NS_FAILED(rv)) { + LOG(("Unable to get a captive portal detector\n")); + return rv; + } + } + + LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n")); + mRequestInProgress = true; + mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this); + return NS_OK; +} + +nsresult +CaptivePortalService::RearmTimer() +{ + LOG(("CaptivePortalService::RearmTimer\n")); + // Start a timer to recheck + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + if (mTimer) { + mTimer->Cancel(); + } + + // If we have successfully determined the state, and we have never detected + // a captive portal, we don't need to keep polling, but will rely on events + // to trigger detection. + if (mState == NOT_CAPTIVE) { + return NS_OK; + } + + if (!mTimer) { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + if (mTimer && mDelay > 0) { + LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay)); + return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +nsresult +CaptivePortalService::Initialize() +{ + if (mInitialized) { + return NS_OK; + } + mInitialized = true; + + // Only the main process service should actually do anything. The service in + // the content process only mirrors the CP state in the main process. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true); + } + + LOG(("Initialized CaptivePortalService\n")); + return NS_OK; +} + +nsresult +CaptivePortalService::Start() +{ + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + if (mStarted) { + return NS_OK; + } + + MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN"); + mStarted = true; + mEverBeenCaptive = false; + + // Get the delay prefs + Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval); + Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval); + Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor); + + LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", + mMinInterval, mMaxInterval, mBackoffFactor)); + + mSlackCount = 0; + mDelay = mMinInterval; + + // When Start is called, perform a check immediately + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +nsresult +CaptivePortalService::Stop() +{ + LOG(("CaptivePortalService::Stop\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything when called in the content process. + return NS_OK; + } + + if (!mStarted) { + return NS_OK; + } + + if (mTimer) { + mTimer->Cancel(); + } + mTimer = nullptr; + mRequestInProgress = false; + mStarted = false; + if (mCaptivePortalDetector) { + mCaptivePortalDetector->Abort(kInterfaceName); + } + mCaptivePortalDetector = nullptr; + + // Clear the state in case anyone queries the state while detection is off. + mState = UNKNOWN; + return NS_OK; +} + +void +CaptivePortalService::SetStateInChild(int32_t aState) +{ + // This should only be called in the content process, from ContentChild.cpp + // in order to mirror the captive portal state set in the chrome process. + MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default); + + mState = aState; + mLastChecked = TimeStamp::Now(); +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalService +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetState(int32_t *aState) +{ + *aState = mState; + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::RecheckCaptivePortal() +{ + LOG(("CaptivePortalService::RecheckCaptivePortal\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + // This is called for user activity. We need to reset the slack count, + // so the checks continue to be quite frequent. + mSlackCount = 0; + mDelay = mMinInterval; + + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::GetLastChecked(uint64_t *aLastChecked) +{ + double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds(); + *aLastChecked = static_cast(duration); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsITimer +// This callback gets called every mDelay miliseconds +// It issues a checkCaptivePortal operation if one isn't already in progress +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Notify(nsITimer *aTimer) +{ + LOG(("CaptivePortalService::Notify\n")); + MOZ_ASSERT(aTimer == mTimer); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + PerformCheck(); + + // This is needed because we don't want to always make requests very often. + // Every 10 checks, we the delay is increased mBackoffFactor times + // to a maximum delay of mMaxInterval + mSlackCount++; + if (mSlackCount % 10 == 0) { + mDelay = mDelay * mBackoffFactor; + } + if (mDelay > mMaxInterval) { + mDelay = mMaxInterval; + } + + // Note - if mDelay is 0, the timer will not be rearmed. + RearmTimer(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsIObserver +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Observe(nsISupports *aSubject, + const char * aTopic, + const char16_t * aData) +{ + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic)); + if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { + // A redirect or altered content has been detected. + // The user needs to log in. We are in a captive portal. + mState = LOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mEverBeenCaptive = true; + } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { + // The user has successfully logged in. We have connectivity. + mState = UNLOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + mDelay = mMinInterval; + + RearmTimer(); + } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { + // The login has been aborted + mState = UNKNOWN; + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + } + + // Send notification so that the captive portal state is mirrored in the + // content process. + nsCOMPtr observerService = services::GetObserverService(); + if (observerService) { + nsCOMPtr cps(this); + observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, nullptr); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalCallback +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Prepare() +{ + LOG(("CaptivePortalService::Prepare\n")); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + // XXX: Finish preparation shouldn't be called until dns and routing is available. + if (mCaptivePortalDetector) { + mCaptivePortalDetector->FinishPreparation(kInterfaceName); + } + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::Complete(bool success) +{ + LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, mState)); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + mLastChecked = TimeStamp::Now(); + + // Note: this callback gets called when: + // 1. the request is completed, and content is valid (success == true) + // 2. when the request is aborted or times out (success == false) + + if (success) { + if (mEverBeenCaptive) { + mState = UNLOCKED_PORTAL; + } else { + mState = NOT_CAPTIVE; + } + } + + mRequestInProgress = false; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h new file mode 100644 index 000000000..dd15450dc --- /dev/null +++ b/netwerk/base/CaptivePortalService.h @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CaptivePortalService_h_ +#define CaptivePortalService_h_ + +#include "nsICaptivePortalService.h" +#include "nsICaptivePortalDetector.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsITimer.h" +#include "nsCOMArray.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace net { + +class CaptivePortalService + : public nsICaptivePortalService + , public nsIObserver + , public nsSupportsWeakReference + , public nsITimerCallback + , public nsICaptivePortalCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAPTIVEPORTALSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICAPTIVEPORTALCALLBACK + + CaptivePortalService(); + nsresult Initialize(); + nsresult Start(); + nsresult Stop(); + + // This method is only called in the content process, in order to mirror + // the captive portal state in the parent process. + void SetStateInChild(int32_t aState); +private: + virtual ~CaptivePortalService(); + nsresult PerformCheck(); + nsresult RearmTimer(); + + nsCOMPtr mCaptivePortalDetector; + int32_t mState; + + nsCOMPtr mTimer; + bool mStarted; + bool mInitialized; + bool mRequestInProgress; + bool mEverBeenCaptive; + + uint32_t mDelay; + int32_t mSlackCount; + + uint32_t mMinInterval; + uint32_t mMaxInterval; + float mBackoffFactor; + + // This holds a timestamp when the last time when the captive portal check + // has changed state. + mozilla::TimeStamp mLastChecked; +}; + +} // namespace net +} // namespace mozilla + +#endif // CaptivePortalService_h_ diff --git a/netwerk/base/ChannelDiverterChild.cpp b/netwerk/base/ChannelDiverterChild.cpp new file mode 100644 index 000000000..d275783f8 --- /dev/null +++ b/netwerk/base/ChannelDiverterChild.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/ChannelDiverterChild.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/FTPChannelChild.h" +#include "mozilla/net/PHttpChannelChild.h" +#include "mozilla/net/PFTPChannelChild.h" +#include "nsIDivertableChannel.h" + +namespace mozilla { +namespace net { + +ChannelDiverterChild::ChannelDiverterChild() +{ +} + +ChannelDiverterChild::~ChannelDiverterChild() +{ +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ChannelDiverterChild.h b/netwerk/base/ChannelDiverterChild.h new file mode 100644 index 000000000..a92de2f11 --- /dev/null +++ b/netwerk/base/ChannelDiverterChild.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _channeldiverterchild_h_ +#define _channeldiverterchild_h_ + +#include "mozilla/net/PChannelDiverterChild.h" + +namespace mozilla { +namespace net { + +class ChannelDiverterChild : + public PChannelDiverterChild +{ +public: + ChannelDiverterChild(); + virtual ~ChannelDiverterChild(); +}; + +} // namespace net +} // namespace mozilla + +#endif /* _channeldiverterchild_h_ */ diff --git a/netwerk/base/ChannelDiverterParent.cpp b/netwerk/base/ChannelDiverterParent.cpp new file mode 100644 index 000000000..3b66644ab --- /dev/null +++ b/netwerk/base/ChannelDiverterParent.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/ChannelDiverterParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/net/HttpChannelParent.h" +#include "mozilla/net/FTPChannelParent.h" +#include "mozilla/net/PHttpChannelParent.h" +#include "mozilla/net/PFTPChannelParent.h" +#include "ADivertableParentChannel.h" + +namespace mozilla { +namespace net { + +ChannelDiverterParent::ChannelDiverterParent() +{ +} + +ChannelDiverterParent::~ChannelDiverterParent() +{ +} + +bool +ChannelDiverterParent::Init(const ChannelDiverterArgs& aArgs) +{ + switch (aArgs.type()) { + case ChannelDiverterArgs::THttpChannelDiverterArgs: + { + auto httpParent = static_cast( + aArgs.get_HttpChannelDiverterArgs().mChannelParent()); + httpParent->SetApplyConversion(aArgs.get_HttpChannelDiverterArgs().mApplyConversion()); + + mDivertableChannelParent = + static_cast(httpParent); + break; + } + case ChannelDiverterArgs::TPFTPChannelParent: + { + mDivertableChannelParent = static_cast( + static_cast(aArgs.get_PFTPChannelParent())); + break; + } + default: + NS_NOTREACHED("unknown ChannelDiverterArgs type"); + return false; + } + MOZ_ASSERT(mDivertableChannelParent); + + nsresult rv = mDivertableChannelParent->SuspendForDiversion(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return true; +} + +void +ChannelDiverterParent::DivertTo(nsIStreamListener* newListener) +{ + MOZ_ASSERT(newListener); + MOZ_ASSERT(mDivertableChannelParent); + + mDivertableChannelParent->DivertTo(newListener); +} + +void +ChannelDiverterParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005179 +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ChannelDiverterParent.h b/netwerk/base/ChannelDiverterParent.h new file mode 100644 index 000000000..047e1c68a --- /dev/null +++ b/netwerk/base/ChannelDiverterParent.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _channeldiverterparent_h_ +#define _channeldiverterparent_h_ + +#include "mozilla/net/PChannelDiverterParent.h" + +class nsIStreamListener; + +namespace mozilla { +namespace net { + +class ChannelDiverterArgs; +class ADivertableParentChannel; + +class ChannelDiverterParent : + public PChannelDiverterParent +{ +public: + ChannelDiverterParent(); + virtual ~ChannelDiverterParent(); + + bool Init(const ChannelDiverterArgs& aArgs); + + void DivertTo(nsIStreamListener* newListener); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + RefPtr mDivertableChannelParent; +}; + +} // namespace net +} // namespace mozilla + +#endif /* _channeldiverterparent_h_ */ diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp new file mode 100644 index 000000000..f5d0880ae --- /dev/null +++ b/netwerk/base/Dashboard.cpp @@ -0,0 +1,921 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http:mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/NetDashboardBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/net/Dashboard.h" +#include "mozilla/net/HttpInfo.h" +#include "nsHttp.h" +#include "nsICancelable.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsIInputStream.h" +#include "nsISocketTransport.h" +#include "nsIThread.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsURLHelper.h" +#include "mozilla/Logging.h" + +using mozilla::AutoSafeJSContext; +using mozilla::dom::Sequence; +using mozilla::dom::ToJSValue; + +namespace mozilla { +namespace net { + +class SocketData + : public nsISupports +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + SocketData() + { + mTotalSent = 0; + mTotalRecv = 0; + mThread = nullptr; + } + + uint64_t mTotalSent; + uint64_t mTotalRecv; + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; + +private: + virtual ~SocketData() + { + } +}; + +static void GetErrorString(nsresult rv, nsAString& errorString); + +NS_IMPL_ISUPPORTS0(SocketData) + + +class HttpData + : public nsISupports +{ + virtual ~HttpData() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + HttpData() + { + mThread = nullptr; + } + + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(HttpData) + + +class WebSocketRequest + : public nsISupports +{ + virtual ~WebSocketRequest() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + WebSocketRequest() + { + mThread = nullptr; + } + + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(WebSocketRequest) + + +class DnsData + : public nsISupports +{ + virtual ~DnsData() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + DnsData() + { + mThread = nullptr; + } + + nsTArray mData; + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(DnsData) + + +class ConnectionData + : public nsITransportEventSink + , public nsITimerCallback +{ + virtual ~ConnectionData() + { + if (mTimer) { + mTimer->Cancel(); + } + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSITIMERCALLBACK + + void StartTimer(uint32_t aTimeout); + void StopTimer(); + + explicit ConnectionData(Dashboard *target) + { + mThread = nullptr; + mDashboard = target; + } + + nsCOMPtr mSocket; + nsCOMPtr mStreamIn; + nsCOMPtr mTimer; + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; + Dashboard *mDashboard; + + nsCString mHost; + uint32_t mPort; + const char *mProtocol; + uint32_t mTimeout; + + nsString mStatus; +}; + +NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback) + +NS_IMETHODIMP +ConnectionData::OnTransportStatus(nsITransport *aTransport, nsresult aStatus, + int64_t aProgress, int64_t aProgressMax) +{ + if (aStatus == NS_NET_STATUS_CONNECTED_TO) { + StopTimer(); + } + + GetErrorString(aStatus, mStatus); + mThread->Dispatch(NewRunnableMethod> + (mDashboard, &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +ConnectionData::Notify(nsITimer *aTimer) +{ + MOZ_ASSERT(aTimer == mTimer); + + if (mSocket) { + mSocket->Close(NS_ERROR_ABORT); + mSocket = nullptr; + mStreamIn = nullptr; + } + + mTimer = nullptr; + + mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT"); + mThread->Dispatch(NewRunnableMethod> + (mDashboard, &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +ConnectionData::StartTimer(uint32_t aTimeout) +{ + if (!mTimer) { + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + } + + mTimer->InitWithCallback(this, aTimeout * 1000, + nsITimer::TYPE_ONE_SHOT); +} + +void +ConnectionData::StopTimer() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + + +class LookupHelper; + +class LookupArgument + : public nsISupports +{ + virtual ~LookupArgument() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + LookupArgument(nsIDNSRecord *aRecord, LookupHelper *aHelper) + { + mRecord = aRecord; + mHelper = aHelper; + } + + nsCOMPtr mRecord; + RefPtr mHelper; +}; + +NS_IMPL_ISUPPORTS0(LookupArgument) + + +class LookupHelper + : public nsIDNSListener +{ + virtual ~LookupHelper() + { + if (mCancel) { + mCancel->Cancel(NS_ERROR_ABORT); + } + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + LookupHelper() { + } + + nsresult ConstructAnswer(LookupArgument *aArgument); +public: + nsCOMPtr mCancel; + nsMainThreadPtrHandle mCallback; + nsIThread *mThread; + nsresult mStatus; +}; + +NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener) + +NS_IMETHODIMP +LookupHelper::OnLookupComplete(nsICancelable *aRequest, + nsIDNSRecord *aRecord, nsresult aStatus) +{ + MOZ_ASSERT(aRequest == mCancel); + mCancel = nullptr; + mStatus = aStatus; + + RefPtr arg = new LookupArgument(aRecord, this); + mThread->Dispatch(NewRunnableMethod> + (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(); + + if (NS_SUCCEEDED(mStatus)) { + dict.mAnswer = true; + bool hasMore; + aRecord->HasMore(&hasMore); + while (hasMore) { + nsString* nextAddress = addresses.AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString nextAddressASCII; + aRecord->GetNextAddrAsString(nextAddressASCII); + CopyASCIItoUTF16(nextAddressASCII, *nextAddress); + aRecord->HasMore(&hasMore); + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + this->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier) + +Dashboard::Dashboard() +{ + mEnableLogging = false; +} + +Dashboard::~Dashboard() +{ +} + +NS_IMETHODIMP +Dashboard::RequestSockets(NetDashboardCallback *aCallback) +{ + RefPtr socketData = new SocketData(); + socketData->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + socketData->mThread = NS_GetCurrentThread(); + gSocketTransportService->Dispatch(NewRunnableMethod> + (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->mThread->Dispatch(NewRunnableMethod> + (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; + mSocket.mTcp = socketData->mData[i].tcp; + mSocket.mSent = (double) socketData->mData[i].sent; + mSocket.mReceived = (double) socketData->mData[i].received; + dict.mSent += socketData->mData[i].sent; + dict.mReceived += socketData->mData[i].received; + } + + dict.mSent += socketData->mTotalSent; + dict.mReceived += socketData->mTotalRecv; + JS::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) + return NS_ERROR_FAILURE; + socketData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestHttpConnections(NetDashboardCallback *aCallback) +{ + RefPtr httpData = new HttpData(); + httpData->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + httpData->mThread = NS_GetCurrentThread(); + + gSocketTransportService->Dispatch(NewRunnableMethod> + (this, &Dashboard::GetHttpDispatch, httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult +Dashboard::GetHttpDispatch(HttpData *aHttpData) +{ + RefPtr httpData = aHttpData; + HttpInfo::GetHttpConnectionData(&httpData->mData); + httpData->mThread->Dispatch(NewRunnableMethod> + (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::HalfOpenInfoDict; + 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; + connection.mSpdy = httpData->mData[i].spdy; + connection.mSsl = httpData->mData[i].ssl; + + connection.mActive.Construct(); + connection.mIdle.Construct(); + connection.mHalfOpens.Construct(); + + Sequence &active = connection.mActive.Value(); + Sequence &idle = connection.mIdle.Value(); + Sequence &halfOpens = connection.mHalfOpens.Value(); + + if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) || + !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) || + !halfOpens.SetCapacity(httpData->mData[i].halfOpens.Length(), + fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) { + HttpConnInfo &info = *active.AppendElement(fallible); + info.mRtt = httpData->mData[i].active[j].rtt; + info.mTtl = httpData->mData[i].active[j].ttl; + info.mProtocolVersion = + httpData->mData[i].active[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) { + HttpConnInfo &info = *idle.AppendElement(fallible); + info.mRtt = httpData->mData[i].idle[j].rtt; + info.mTtl = httpData->mData[i].idle[j].ttl; + info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].halfOpens.Length(); j++) { + HalfOpenInfoDict &info = *halfOpens.AppendElement(fallible); + info.mSpeculative = httpData->mData[i].halfOpens[j].speculative; + } + } + + JS::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + httpData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::GetEnableLogging(bool *value) +{ + *value = mEnableLogging; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::SetEnableLogging(const bool value) +{ + mEnableLogging = value; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) +{ + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + LogData mData(nsCString(aHost), aSerial, aEncrypted); + if (mWs.data.Contains(mData)) { + return NS_OK; + } + if (!mWs.data.AppendElement(mData)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) +{ + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) + return NS_ERROR_FAILURE; + mWs.data.RemoveElementAt(index); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, uint32_t aLength) +{ + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) + return NS_ERROR_FAILURE; + mWs.data[index].mMsgSent++; + mWs.data[index].mSizeSent += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, uint32_t aLength) +{ + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) + return NS_ERROR_FAILURE; + mWs.data[index].mMsgReceived++; + mWs.data[index].mSizeReceived += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RequestWebsocketConnections(NetDashboardCallback *aCallback) +{ + RefPtr wsRequest = new WebSocketRequest(); + wsRequest->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + wsRequest->mThread = NS_GetCurrentThread(); + + wsRequest->mThread->Dispatch(NewRunnableMethod> + (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::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + wsRequest->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSInfo(NetDashboardCallback *aCallback) +{ + RefPtr dnsData = new DnsData(); + dnsData->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + + nsresult rv; + dnsData->mData.Clear(); + dnsData->mThread = NS_GetCurrentThread(); + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + gSocketTransportService->Dispatch(NewRunnableMethod> + (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->mThread->Dispatch(NewRunnableMethod> + (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; + + for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) { + nsString* addr = addrs.AppendElement(fallible); + if (!addr) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr); + } + + if (dnsData->mData[i].family == PR_AF_INET6) { + CopyASCIItoUTF16("ipv6", entry.mFamily); + } else { + CopyASCIItoUTF16("ipv4", entry.mFamily); + } + } + + JS::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + dnsData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSLookup(const nsACString &aHost, + NetDashboardCallback *aCallback) +{ + nsresult rv; + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr helper = new LookupHelper(); + helper->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + helper->mThread = NS_GetCurrentThread(); + rv = mDnsService->AsyncResolve(aHost, 0, helper.get(), + NS_GetCurrentThread(), + getter_AddRefs(helper->mCancel)); + return rv; +} + +void +HttpConnInfo::SetHTTP1ProtocolVersion(uint8_t pv) +{ + switch (pv) { + case NS_HTTP_VERSION_0_9: + protocolVersion.AssignLiteral(u"http/0.9"); + break; + case NS_HTTP_VERSION_1_0: + protocolVersion.AssignLiteral(u"http/1.0"); + break; + case NS_HTTP_VERSION_1_1: + protocolVersion.AssignLiteral(u"http/1.1"); + break; + case NS_HTTP_VERSION_2_0: + protocolVersion.AssignLiteral(u"http/2.0"); + break; + default: + protocolVersion.AssignLiteral(u"unknown protocol version"); + } +} + +void +HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv) +{ + MOZ_ASSERT (pv == HTTP_VERSION_2); + protocolVersion.Assign(u"h2"); +} + +NS_IMETHODIMP +Dashboard::GetLogPath(nsACString &aLogPath) +{ + aLogPath.SetCapacity(2048); + uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048); + aLogPath.SetLength(len); + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort, + const char *aProtocol, uint32_t aTimeout, + NetDashboardCallback *aCallback) +{ + nsresult rv; + RefPtr connectionData = new ConnectionData(this); + connectionData->mHost = aHost; + connectionData->mPort = aPort; + connectionData->mProtocol = aProtocol; + connectionData->mTimeout = aTimeout; + + connectionData->mCallback = + new nsMainThreadPtrHolder(aCallback, true); + connectionData->mThread = NS_GetCurrentThread(); + + rv = TestNewConnection(connectionData); + if (NS_FAILED(rv)) { + mozilla::net::GetErrorString(rv, connectionData->mStatus); + connectionData->mThread->Dispatch(NewRunnableMethod> + (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::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) + return NS_ERROR_FAILURE; + + connectionData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +nsresult +Dashboard::TestNewConnection(ConnectionData *aConnectionData) +{ + RefPtr connectionData = aConnectionData; + + nsresult rv; + if (!connectionData->mHost.Length() || + !net_IsValidHostName(connectionData->mHost)) { + return NS_ERROR_UNKNOWN_HOST; + } + + if (connectionData->mProtocol && + NS_LITERAL_STRING("ssl").EqualsASCII(connectionData->mProtocol)) { + rv = gSocketTransportService->CreateTransport( + &connectionData->mProtocol, 1, connectionData->mHost, + connectionData->mPort, nullptr, + getter_AddRefs(connectionData->mSocket)); + } else { + rv = gSocketTransportService->CreateTransport( + nullptr, 0, connectionData->mHost, + connectionData->mPort, nullptr, + getter_AddRefs(connectionData->mSocket)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->SetEventSink(connectionData, + NS_GetCurrentThread()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->OpenInputStream( + nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(connectionData->mStreamIn)); + if (NS_FAILED(rv)) { + return rv; + } + + connectionData->StartTimer(connectionData->mTimeout); + + return rv; +} + +typedef struct +{ + nsresult key; + const char *error; +} ErrorEntry; + +#undef ERROR +#define ERROR(key, val) {key, #key} + +ErrorEntry socketTransportStatuses[] = { + ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), + ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), + ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), + ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), + ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), + ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), + ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), +}; +#undef ERROR + + +static void +GetErrorString(nsresult rv, nsAString& errorString) +{ + for (size_t i = 0; i < ArrayLength(socketTransportStatuses); ++i) { + if (socketTransportStatuses[i].key == rv) { + errorString.AssignASCII(socketTransportStatuses[i].error); + return; + } + } + nsAutoCString errorCString; + mozilla::GetErrorName(rv, errorCString); + CopyUTF8toUTF16(errorCString, errorString); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h new file mode 100644 index 000000000..4e893c15d --- /dev/null +++ b/netwerk/base/Dashboard.h @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDashboard_h__ +#define nsDashboard_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/net/DashboardTypes.h" +#include "nsIDashboard.h" +#include "nsIDashboardEventNotifier.h" +#include "nsIDNSListener.h" +#include "nsIServiceManager.h" +#include "nsITimer.h" +#include "nsITransport.h" + +class nsIDNSService; + +namespace mozilla { +namespace net { + +class SocketData; +class HttpData; +class DnsData; +class WebSocketRequest; +class ConnectionData; + +class Dashboard final + : public nsIDashboard + , public nsIDashboardEventNotifier +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDASHBOARD + NS_DECL_NSIDASHBOARDEVENTNOTIFIER + + Dashboard(); + static const char *GetErrorString(nsresult rv); + nsresult GetConnectionStatus(ConnectionData *aConnectionData); + +private: + + struct LogData + { + LogData(nsCString host, uint32_t serial, bool encryption): + mHost(host), + mSerial(serial), + mMsgSent(0), + mMsgReceived(0), + mSizeSent(0), + mSizeReceived(0), + mEncrypted(encryption) + { } + nsCString mHost; + uint32_t mSerial; + uint32_t mMsgSent; + uint32_t mMsgReceived; + uint64_t mSizeSent; + uint64_t mSizeReceived; + bool mEncrypted; + bool operator==(const LogData& a) const + { + return mHost.Equals(a.mHost) && (mSerial == a.mSerial); + } + }; + + struct WebSocketData + { + WebSocketData():lock("Dashboard.webSocketData") + { + } + uint32_t IndexOf(nsCString hostname, uint32_t mSerial) + { + LogData temp(hostname, mSerial, false); + return data.IndexOf(temp); + } + nsTArray data; + mozilla::Mutex lock; + }; + + + bool mEnableLogging; + WebSocketData mWs; + +private: + virtual ~Dashboard(); + + nsresult GetSocketsDispatch(SocketData *); + nsresult GetHttpDispatch(HttpData *); + nsresult GetDnsInfoDispatch(DnsData *); + nsresult TestNewConnection(ConnectionData *); + + /* Helper methods that pass the JSON to the callback function. */ + nsresult GetSockets(SocketData *); + nsresult GetHttpConnections(HttpData *); + nsresult GetDNSCacheEntries(DnsData *); + nsresult GetWebSocketConnections(WebSocketRequest *); + + nsCOMPtr mDnsService; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsDashboard_h__ diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h new file mode 100644 index 000000000..3f1f30d16 --- /dev/null +++ b/netwerk/base/DashboardTypes.h @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_DashboardTypes_h_ +#define mozilla_net_DashboardTypes_h_ + +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +struct SocketInfo +{ + nsCString host; + uint64_t sent; + uint64_t received; + uint16_t port; + bool active; + bool tcp; +}; + +struct HalfOpenSockets +{ + bool speculative; +}; + +struct DNSCacheEntries +{ + nsCString hostname; + nsTArray hostaddr; + uint16_t family; + int64_t expiration; + nsCString netInterface; +}; + +struct HttpConnInfo +{ + uint32_t ttl; + uint32_t rtt; + nsString protocolVersion; + + void SetHTTP1ProtocolVersion(uint8_t pv); + void SetHTTP2ProtocolVersion(uint8_t pv); +}; + +struct HttpRetParams +{ + nsCString host; + nsTArray active; + nsTArray idle; + nsTArray halfOpens; + uint32_t counter; + uint16_t port; + bool spdy; + bool ssl; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DashboardTypes_h_ diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp new file mode 100644 index 000000000..e12624ea2 --- /dev/null +++ b/netwerk/base/EventTokenBucket.cpp @@ -0,0 +1,462 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EventTokenBucket.h" + +#include "nsICancelable.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransportService2.h" +#ifdef DEBUG +#include "MainThreadUtils.h" +#endif + +#ifdef XP_WIN +#include +#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() {} + + friend class EventTokenBucket; + ATokenBucketEvent *mEvent; +}; + +NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) + +TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event) + : mEvent(event) +{ +} + +NS_IMETHODIMP +TokenBucketCancelable::Cancel(nsresult reason) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + mEvent = nullptr; + return NS_OK; +} + +void +TokenBucketCancelable::Fire() +{ + if (!mEvent) + return; + + ATokenBucketEvent *event = mEvent; + mEvent = nullptr; + event->OnTokenBucketAdmitted(); +} + +//////////////////////////////////////////// +// EventTokenBucket +//////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback) + +// by default 1hz with no burst +EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, + uint32_t burstSize) + : mUnitCost(kUsecPerSec) + , mMaxCredit(kUsecPerSec) + , mCredit(kUsecPerSec) + , mPaused(false) + , mStopped(false) + , mTimerArmed(false) +#ifdef XP_WIN + , mFineGrainTimerInUse(false) + , mFineGrainResetTimerArmed(false) +#endif +{ + mLastUpdate = TimeStamp::Now(); + + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr sts; + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mTimer) + mTimer->SetTarget(sts); + SetRate(eventsPerSecond, burstSize); +} + +EventTokenBucket::~EventTokenBucket() +{ + SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n", + this, mEvents.GetSize())); + + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr cancelable = + dont_AddRef(static_cast(mEvents.PopFront())); + cancelable->Fire(); + } +} + +void +EventTokenBucket::CleanupTimers() +{ + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; + mTimerArmed = false; + +#ifdef XP_WIN + NormalTimers(); + if (mFineGrainResetTimer && mFineGrainResetTimerArmed) { + mFineGrainResetTimer->Cancel(); + } + mFineGrainResetTimer = nullptr; + mFineGrainResetTimerArmed = false; +#endif +} + +void +EventTokenBucket::SetRate(uint32_t eventsPerSecond, + uint32_t burstSize) +{ + SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", + this, eventsPerSecond, burstSize)); + + if (eventsPerSecond > kMaxHz) { + eventsPerSecond = kMaxHz; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + if (!eventsPerSecond) { + eventsPerSecond = 1; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + mUnitCost = kUsecPerSec / eventsPerSecond; + mMaxCredit = mUnitCost * burstSize; + if (mMaxCredit > kUsecPerSec * 60 * 15) { + SOCKET_LOG((" burstSize out of range\n")); + mMaxCredit = kUsecPerSec * 60 * 15; + } + mCredit = mMaxCredit; + mLastUpdate = TimeStamp::Now(); +} + +void +EventTokenBucket::ClearCredits() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); + mCredit = 0; +} + +uint32_t +EventTokenBucket::BurstEventsAvailable() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return static_cast(mCredit / mUnitCost); +} + +uint32_t +EventTokenBucket::QueuedEvents() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mEvents.GetSize(); +} + +void +EventTokenBucket::Pause() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::Pause %p\n", this)); + if (mPaused || mStopped) + return; + + mPaused = true; + if (mTimerArmed) { + mTimer->Cancel(); + mTimerArmed = false; + } +} + +void +EventTokenBucket::UnPause() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this)); + if (!mPaused || mStopped) + return; + + mPaused = false; + DispatchEvents(); + UpdateTimer(); +} + +void +EventTokenBucket::Stop() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed)); + mStopped = true; + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr cancelable = + dont_AddRef(static_cast(mEvents.PopFront())); + cancelable->Fire(); + } +} + +nsresult +EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this)); + + if (mStopped || !mTimer) + return NS_ERROR_FAILURE; + + UpdateCredits(); + + RefPtr cancelEvent = new TokenBucketCancelable(event); + // When this function exits the cancelEvent needs 2 references, one for the + // mEvents queue and one for the caller of SubmitEvent() + + NS_ADDREF(*cancelable = cancelEvent.get()); + + if (mPaused || !TryImmediateDispatch(cancelEvent.get())) { + // queue it + SOCKET_LOG((" queued\n")); + mEvents.Push(cancelEvent.forget().take()); + UpdateTimer(); + } + else { + SOCKET_LOG((" dispatched synchronously\n")); + } + + return NS_OK; +} + +bool +EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable *cancelable) +{ + if (mCredit < mUnitCost) + return false; + + mCredit -= mUnitCost; + cancelable->Fire(); + return true; +} + +void +EventTokenBucket::DispatchEvents() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused)); + if (mPaused || mStopped) + return; + + while (mEvents.GetSize() && mUnitCost <= mCredit) { + RefPtr cancelable = + dont_AddRef(static_cast(mEvents.PopFront())); + if (cancelable->mEvent) { + SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] " + "Dispatching queue token bucket event cost=%lu credit=%lu\n", + this, mUnitCost, mCredit)); + mCredit -= mUnitCost; + cancelable->Fire(); + } + } + +#ifdef XP_WIN + if (!mEvents.GetSize()) + WantNormalTimers(); +#endif +} + +void +EventTokenBucket::UpdateTimer() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) + return; + + if (mCredit >= mUnitCost) + return; + + // determine the time needed to wait to accumulate enough credits to admit + // one more event and set the timer for that point. Always round it + // up because firing early doesn't help. + // + uint64_t deficit = mUnitCost - mCredit; + uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec; + + if (msecWait < 4) // minimum wait + msecWait = 4; + else if (msecWait > 60000) // maximum wait + msecWait = 60000; + +#ifdef XP_WIN + FineGrainTimers(); +#endif + + SOCKET_LOG(("EventTokenBucket::UpdateTimer %p for %dms\n", + this, msecWait)); + nsresult rv = mTimer->InitWithCallback(this, static_cast(msecWait), + nsITimer::TYPE_ONE_SHOT); + mTimerArmed = NS_SUCCEEDED(rv); +} + +NS_IMETHODIMP +EventTokenBucket::Notify(nsITimer *timer) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + +#ifdef XP_WIN + if (timer == mFineGrainResetTimer) { + FineGrainResetTimerNotify(); + return NS_OK; + } +#endif + + SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this)); + mTimerArmed = false; + if (mStopped) + return NS_OK; + + UpdateCredits(); + DispatchEvents(); + UpdateTimer(); + + return NS_OK; +} + +void +EventTokenBucket::UpdateCredits() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + TimeStamp now = TimeStamp::Now(); + TimeDuration elapsed = now - mLastUpdate; + mLastUpdate = now; + + mCredit += static_cast(elapsed.ToMicroseconds()); + if (mCredit > mMaxCredit) + mCredit = mMaxCredit; + SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %lu (%lu each.. %3.2f)\n", + this, mCredit, mUnitCost, (double)mCredit / mUnitCost)); +} + +#ifdef XP_WIN +void +EventTokenBucket::FineGrainTimers() +{ + SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n", + this, mFineGrainTimerInUse)); + + mLastFineGrainTimerUse = TimeStamp::Now(); + + if (mFineGrainTimerInUse) + return; + + if (mUnitCost > kCostFineGrainThreshold) + return; + + SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", + this)); + + mFineGrainTimerInUse = true; + timeBeginPeriod(1); +} + +void +EventTokenBucket::NormalTimers() +{ + if (!mFineGrainTimerInUse) + return; + mFineGrainTimerInUse = false; + + SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this)); + timeEndPeriod(1); +} + +void +EventTokenBucket::WantNormalTimers() +{ + if (!mFineGrainTimerInUse) + return; + if (mFineGrainResetTimerArmed) + return; + + TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse); + static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5); + + if (elapsed >= fiveSeconds) { + NormalTimers(); + return; + } + + if (!mFineGrainResetTimer) + mFineGrainResetTimer = do_CreateInstance("@mozilla.org/timer;1"); + + // if we can't delay the reset, just do it now + if (!mFineGrainResetTimer) { + NormalTimers(); + return; + } + + // pad the callback out 100ms to avoid having to round trip this again if the + // timer calls back just a tad early. + SOCKET_LOG(("EventTokenBucket::WantNormalTimers %p " + "Will reset timer granularity after delay", this)); + + mFineGrainResetTimer->InitWithCallback( + this, + static_cast((fiveSeconds - elapsed).ToMilliseconds()) + 100, + nsITimer::TYPE_ONE_SHOT); + mFineGrainResetTimerArmed = true; +} + +void +EventTokenBucket::FineGrainResetTimerNotify() +{ + SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify() events = %d\n", + this, mEvents.GetSize())); + mFineGrainResetTimerArmed = false; + + // If we are currently processing events then wait for the queue to drain + // before trying to reset back to normal timers again + if (!mEvents.GetSize()) + WantNormalTimers(); +} + +#endif + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h new file mode 100644 index 000000000..b187ca7b0 --- /dev/null +++ b/netwerk/base/EventTokenBucket.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetEventTokenBucket_h__ +#define NetEventTokenBucket_h__ + +#include "ARefBase.h" +#include "nsCOMPtr.h" +#include "nsDeque.h" +#include "nsITimer.h" + +#include "mozilla/TimeStamp.h" + +class nsICancelable; + +namespace mozilla { +namespace net { + +/* A token bucket is used to govern the maximum rate a series of events + can be executed at. For instance if your event was "eat a piece of cake" + then a token bucket configured to allow "1 piece per day" would spread + the eating of a 8 piece cake over 8 days even if you tried to eat the + whole thing up front. In a practical sense it 'costs' 1 token to execute + an event and tokens are 'earned' at a particular rate as time goes by. + + The token bucket can be perfectly smooth or allow a configurable amount of + burstiness. A bursty token bucket allows you to save up unused credits, while + a perfectly smooth one would not. A smooth "1 per day" cake token bucket + would require 9 days to eat that cake if you skipped a slice on day 4 + (use the token or lose it), while a token bucket configured with a burst + of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day + 5) and finish the cake in the usual 8 days. + + EventTokenBucket(hz=20, burst=5) creates a token bucket with the following properties: + + + events from an infinite stream will be admitted 20 times per second (i.e. + hz=20 means 1 event per 50 ms). Timers will be used to space things evenly down to + 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than 200hz will admit + multiple events with 5ms gaps between them. 10000hz is the maximum rate and 1hz is + the minimum rate. + + + The burst size controls the limit of 'credits' that a token bucket can accumulate + when idle. For our (20,5) example each event requires 50ms of credit (again, 20hz = 50ms + per event). a burst size of 5 means that the token bucket can accumulate a + maximum of 250ms (5 * 50ms) for this bucket. If no events have been admitted for the + last full second the bucket can still only accumulate 250ms of credit - but that credit + means that 5 events can be admitted without delay. A burst size of 1 is the minimum. + The EventTokenBucket is created with maximum credits already applied, but they + can be cleared with the ClearCredits() method. The maximum burst size is + 15 minutes worth of events. + + + An event is submitted to the token bucket asynchronously through SubmitEvent(). + The OnTokenBucketAdmitted() method of the submitted event is used as a callback + when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller + for use in the case they do not wish to wait for the callback. +*/ + +class EventTokenBucket; + +class ATokenBucketEvent +{ +public: + virtual void OnTokenBucketAdmitted() = 0; +}; + +class TokenBucketCancelable; + +class EventTokenBucket : public nsITimerCallback, public ARefBase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + // This should be constructed on the main thread + EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize); + + // These public methods are all meant to be called from the socket thread + void ClearCredits(); + uint32_t BurstEventsAvailable(); + uint32_t QueuedEvents(); + + // a paused token bucket will not process any events, but it will accumulate + // credits. ClearCredits can be used before unpausing if desired. + void Pause(); + void UnPause(); + void Stop(); + + // The returned cancelable event can only be canceled from the socket thread + nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable); + +private: + virtual ~EventTokenBucket(); + void CleanupTimers(); + + friend class RunNotifyEvent; + friend class SetTimerEvent; + + bool TryImmediateDispatch(TokenBucketCancelable *event); + void SetRate(uint32_t eventsPerSecond, uint32_t burstSize); + + void DispatchEvents(); + void UpdateTimer(); + void UpdateCredits(); + + const static uint64_t kUsecPerSec = 1000000; + const static uint64_t kUsecPerMsec = 1000; + const static uint64_t kMaxHz = 10000; + + uint64_t mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond) + uint64_t mMaxCredit; // usec mCredit limit (from busrtSize) + uint64_t mCredit; // usec of accumulated credit. + + bool mPaused; + bool mStopped; + nsDeque mEvents; + bool mTimerArmed; + TimeStamp mLastUpdate; + + // The timer is created on the main thread, but is armed and executes Notify() + // callbacks on the socket thread in order to maintain low latency of event + // delivery. + nsCOMPtr 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/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp new file mode 100644 index 000000000..61b9394f9 --- /dev/null +++ b/netwerk/base/LoadContextInfo.cpp @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LoadContextInfo.h" + +#include "mozilla/dom/ToJSValue.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIWebNavigation.h" +#include "nsNetUtil.h" + +using namespace mozilla::dom; +namespace mozilla { +namespace net { + +// LoadContextInfo + +NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo) + +LoadContextInfo::LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes) + : mIsAnonymous(aIsAnonymous) + , mOriginAttributes(aOriginAttributes) +{ +} + +LoadContextInfo::~LoadContextInfo() +{ +} + +NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool *aIsPrivate) +{ + *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous) +{ + *aIsAnonymous = mIsAnonymous; + return NS_OK; +} + +NeckoOriginAttributes const* LoadContextInfo::OriginAttributesPtr() +{ + return &mOriginAttributes; +} + +NS_IMETHODIMP LoadContextInfo::GetOriginAttributes(JSContext *aCx, + JS::MutableHandle 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, NeckoOriginAttributes()); + info.forget(aDefault); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(nsILoadContextInfo * *aPrivate) +{ + NeckoOriginAttributes 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, NeckoOriginAttributes()); + info.forget(aAnonymous); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::Custom(bool aAnonymous, + JS::HandleValue aOriginAttributes, JSContext *cx, + nsILoadContextInfo * *_retval) +{ + NeckoOriginAttributes attrs; + bool status = attrs.Init(cx, aOriginAttributes); + NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); + + nsCOMPtr 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); + } + + NeckoOriginAttributes oa; + NS_GetOriginAttributes(aChannel, oa); + MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0)); + + return new LoadContextInfo(anon, oa); +} + +LoadContextInfo * +GetLoadContextInfo(nsILoadContext *aLoadContext, bool aIsAnonymous) +{ + if (!aLoadContext) { + return new LoadContextInfo(aIsAnonymous, + NeckoOriginAttributes(nsILoadContextInfo::NO_APP_ID, false)); + } + + DebugOnly pb = aLoadContext->UsePrivateBrowsing(); + DocShellOriginAttributes doa; + aLoadContext->GetOriginAttributes(doa); + MOZ_ASSERT(pb == (doa.mPrivateBrowsingId > 0)); + + NeckoOriginAttributes noa; + noa.InheritFromDocShellToNecko(doa); + + return new LoadContextInfo(aIsAnonymous, noa); +} + +LoadContextInfo* +GetLoadContextInfo(nsIDOMWindow *aWindow, + bool aIsAnonymous) +{ + nsCOMPtr 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, + NeckoOriginAttributes const &aOriginAttributes) +{ + return new LoadContextInfo(aIsAnonymous, + aOriginAttributes); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h new file mode 100644 index 000000000..8477dfd1c --- /dev/null +++ b/netwerk/base/LoadContextInfo.h @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsLoadContextInfo_h__ +#define nsLoadContextInfo_h__ + +#include "nsILoadContextInfo.h" + +class nsIChannel; +class nsILoadContext; + +namespace mozilla { +namespace net { + +class LoadContextInfo : public nsILoadContextInfo +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADCONTEXTINFO + + LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes); + +private: + virtual ~LoadContextInfo(); + +protected: + bool mIsAnonymous : 1; + NeckoOriginAttributes mOriginAttributes; +}; + +class LoadContextInfoFactory : public nsILoadContextInfoFactory +{ + virtual ~LoadContextInfoFactory() {} +public: + NS_DECL_ISUPPORTS // deliberately not thread-safe + NS_DECL_NSILOADCONTEXTINFOFACTORY +}; + +LoadContextInfo* +GetLoadContextInfo(nsIChannel *aChannel); + +LoadContextInfo* +GetLoadContextInfo(nsILoadContext *aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* +GetLoadContextInfo(nsIDOMWindow *aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* +GetLoadContextInfo(nsILoadContextInfo *aInfo); + +LoadContextInfo* +GetLoadContextInfo(bool const aIsAnonymous, + NeckoOriginAttributes const &aOriginAttributes); + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp new file mode 100644 index 000000000..216cf559c --- /dev/null +++ b/netwerk/base/LoadInfo.cpp @@ -0,0 +1,925 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/LoadInfo.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozIThirdPartyUtil.h" +#include "nsFrameLoader.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIFrameLoader.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsNullPrincipal.h" + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +static void +InheritOriginAttributes(nsIPrincipal* aLoadingPrincipal, NeckoOriginAttributes& aAttrs) +{ + const PrincipalOriginAttributes attrs = + BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef(); + aAttrs.InheritFromDocToNecko(attrs); +} + +LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType) + : mLoadingPrincipal(aLoadingContext ? + aLoadingContext->NodePrincipal() : aLoadingPrincipal) + , mTriggeringPrincipal(aTriggeringPrincipal ? + aTriggeringPrincipal : mLoadingPrincipal.get()) + , mPrincipalToInherit(nullptr) + , mLoadingContext(do_GetWeakReference(aLoadingContext)) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(aContentPolicyType) + , mTainting(LoadTainting::Basic) + , mUpgradeInsecureRequests(false) + , mVerifySignedContent(false) + , mEnforceSRI(false) + , mForceInheritPrincipalDropped(false) + , mInnerWindowID(0) + , mOuterWindowID(0) + , mParentOuterWindowID(0) + , mFrameOuterWindowID(0) + , mEnforceSecurity(false) + , mInitialSecurityCheckDone(false) + , mIsThirdPartyContext(false) + , mForcePreflight(false) + , mIsPreflight(false) + , mForceHSTSPriming(false) + , mMixedContentWouldBlock(false) +{ + MOZ_ASSERT(mLoadingPrincipal); + MOZ_ASSERT(mTriggeringPrincipal); + +#ifdef DEBUG + // TYPE_DOCUMENT loads initiated by javascript tests will go through + // nsIOService and use the wrong constructor. Don't enforce the + // !TYPE_DOCUMENT check in those cases + bool skipContentTypeCheck = false; + skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion"); +#endif + + // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't + // have a loadingPrincipal + MOZ_ASSERT(skipContentTypeCheck || + mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT); + + // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false meaning + // that consumers of LoadInfo that don't pass a context or pass a context from + // which we can't find a window will default to assuming that they're 1st + // party. It would be nice if we could default "safe" and assume that we are + // 3rd party until proven otherwise. + + // if consumers pass both, aLoadingContext and aLoadingPrincipal + // then the loadingPrincipal must be the same as the node's principal + MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal || + aLoadingContext->NodePrincipal() == aLoadingPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) { + mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + mForceInheritPrincipalDropped = true; + } + + if (aLoadingContext) { + nsCOMPtr contextOuter = aLoadingContext->OwnerDoc()->GetWindow(); + if (contextOuter) { + ComputeIsThirdPartyContext(contextOuter); + mOuterWindowID = contextOuter->WindowID(); + nsCOMPtr parent = contextOuter->GetScriptableParent(); + mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID; + } + + mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID(); + + // When the element being loaded is a frame, we choose the frame's window + // for the window ID and the frame element's window as the parent + // window. This is the behavior that Chrome exposes to add-ons. + // NB: If the frameLoaderOwner doesn't have a frame loader, then the load + // must be coming from an object (such as a plugin) that's loaded into it + // instead of a document being loaded. In that case, treat this object like + // any other non-document-loading element. + nsCOMPtr frameLoaderOwner = + do_QueryInterface(aLoadingContext); + nsCOMPtr fl = frameLoaderOwner ? + frameLoaderOwner->GetFrameLoader() : nullptr; + if (fl) { + nsCOMPtr docShell; + if (NS_SUCCEEDED(fl->GetDocShell(getter_AddRefs(docShell))) && docShell) { + nsCOMPtr outerWindow = do_GetInterface(docShell); + if (outerWindow) { + mFrameOuterWindowID = outerWindow->WindowID(); + } + } + } + + // if the document forces all requests to be upgraded from http to https, then + // we should do that for all requests. If it only forces preloads to be upgraded + // then we should enforce upgrade insecure requests only for preloads. + mUpgradeInsecureRequests = + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) || + (nsContentUtils::IsPreloadType(mInternalContentPolicyType) && + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true)); + + // if owner doc has content signature, we enforce SRI + nsCOMPtr channel = aLoadingContext->OwnerDoc()->GetChannel(); + if (channel) { + nsCOMPtr loadInfo = channel->GetLoadInfo(); + if (loadInfo) { + mEnforceSRI = loadInfo->GetVerifySignedContent(); + } + } + } + + // If CSP requires SRI (require-sri-for), then store that information + // in the loadInfo so we can enforce SRI before loading the subresource. + if (!mEnforceSRI) { + // do not look into the CSP if already true: + // a CSP saying that SRI isn't needed should not + // overrule GetVerifySignedContent + if (aLoadingPrincipal) { + nsCOMPtr csp; + aLoadingPrincipal->GetCsp(getter_AddRefs(csp)); + uint32_t externalType = + nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType); + // csp could be null if loading principal is system principal + if (csp) { + csp->RequireSRIForType(externalType, &mEnforceSRI); + } + // if CSP is delivered via a meta tag, it's speculatively available + // as 'preloadCSP'. If we are preloading a script or style, we have + // to apply that speculative 'preloadCSP' for such loads. + if (!mEnforceSRI && nsContentUtils::IsPreloadType(aContentPolicyType)) { + nsCOMPtr preloadCSP; + aLoadingPrincipal->GetPreloadCsp(getter_AddRefs(preloadCSP)); + if (preloadCSP) { + preloadCSP->RequireSRIForType(externalType, &mEnforceSRI); + } + } + } + } + + InheritOriginAttributes(mLoadingPrincipal, mOriginAttributes); + + // We need to do this after inheriting the document's origin attributes + // above, in case the loading principal ends up being the system principal. + if (aLoadingContext) { + nsCOMPtr loadContext = + aLoadingContext->OwnerDoc()->GetLoadContext(); + nsCOMPtr docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (loadContext && docShell && + docShell->ItemType() == nsIDocShellTreeItem::typeContent) { + bool usePrivateBrowsing; + nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing); + if (NS_SUCCEEDED(rv)) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing); + } + } + } + + // For chrome docshell, the mPrivateBrowsingId remains 0 even its + // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in + // origin attributes if the type of the docshell is content. + if (aLoadingContext) { + nsCOMPtr docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (docShell) { + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + } + } +} + +/* Constructor takes an outer window, but no loadingNode or loadingPrincipal. + * This constructor should only be used for TYPE_DOCUMENT loads, since they + * have a null loadingNode and loadingPrincipal. +*/ +LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow, + nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags) + : mLoadingPrincipal(nullptr) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(nullptr) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) + , mTainting(LoadTainting::Basic) + , mUpgradeInsecureRequests(false) + , mVerifySignedContent(false) + , mEnforceSRI(false) + , mForceInheritPrincipalDropped(false) + , mInnerWindowID(0) + , mOuterWindowID(0) + , mParentOuterWindowID(0) + , mFrameOuterWindowID(0) + , mEnforceSecurity(false) + , mInitialSecurityCheckDone(false) + , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party. + , mForcePreflight(false) + , mIsPreflight(false) + , mForceHSTSPriming(false) + , mMixedContentWouldBlock(false) +{ + // Top-level loads are never third-party + // Grab the information we can out of the window. + MOZ_ASSERT(aOuterWindow); + MOZ_ASSERT(mTriggeringPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) { + mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + mForceInheritPrincipalDropped = true; + } + + // NB: Ignore the current inner window since we're navigating away from it. + mOuterWindowID = aOuterWindow->WindowID(); + + // TODO We can have a parent without a frame element in some cases dealing + // with the hidden window. + nsCOMPtr parent = aOuterWindow->GetScriptableParent(); + mParentOuterWindowID = parent ? parent->WindowID() : 0; + + // get the docshell from the outerwindow, and then get the originattributes + nsCOMPtr docShell = aOuterWindow->GetDocShell(); + MOZ_ASSERT(docShell); + const DocShellOriginAttributes attrs = + nsDocShell::Cast(docShell)->GetOriginAttributes(); + + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + MOZ_ASSERT(attrs.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + + mOriginAttributes.InheritFromDocShellToNecko(attrs); +} + +LoadInfo::LoadInfo(const LoadInfo& rhs) + : mLoadingPrincipal(rhs.mLoadingPrincipal) + , mTriggeringPrincipal(rhs.mTriggeringPrincipal) + , mPrincipalToInherit(rhs.mPrincipalToInherit) + , mLoadingContext(rhs.mLoadingContext) + , mSecurityFlags(rhs.mSecurityFlags) + , mInternalContentPolicyType(rhs.mInternalContentPolicyType) + , mTainting(rhs.mTainting) + , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests) + , mVerifySignedContent(rhs.mVerifySignedContent) + , mEnforceSRI(rhs.mEnforceSRI) + , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped) + , mInnerWindowID(rhs.mInnerWindowID) + , mOuterWindowID(rhs.mOuterWindowID) + , mParentOuterWindowID(rhs.mParentOuterWindowID) + , mFrameOuterWindowID(rhs.mFrameOuterWindowID) + , mEnforceSecurity(rhs.mEnforceSecurity) + , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone) + , mIsThirdPartyContext(rhs.mIsThirdPartyContext) + , mOriginAttributes(rhs.mOriginAttributes) + , mRedirectChainIncludingInternalRedirects( + rhs.mRedirectChainIncludingInternalRedirects) + , mRedirectChain(rhs.mRedirectChain) + , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders) + , mForcePreflight(rhs.mForcePreflight) + , mIsPreflight(rhs.mIsPreflight) + , mForceHSTSPriming(rhs.mForceHSTSPriming) + , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock) +{ +} + +LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, + bool aUpgradeInsecureRequests, + bool aVerifySignedContent, + bool aEnforceSRI, + bool aForceInheritPrincipalDropped, + uint64_t aInnerWindowID, + uint64_t aOuterWindowID, + uint64_t aParentOuterWindowID, + uint64_t aFrameOuterWindowID, + bool aEnforceSecurity, + bool aInitialSecurityCheckDone, + bool aIsThirdPartyContext, + const NeckoOriginAttributes& aOriginAttributes, + nsTArray>& aRedirectChainIncludingInternalRedirects, + nsTArray>& aRedirectChain, + const nsTArray& aCorsUnsafeHeaders, + bool aForcePreflight, + bool aIsPreflight, + bool aForceHSTSPriming, + bool aMixedContentWouldBlock) + : mLoadingPrincipal(aLoadingPrincipal) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(aPrincipalToInherit) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(aContentPolicyType) + , mTainting(aTainting) + , mUpgradeInsecureRequests(aUpgradeInsecureRequests) + , mVerifySignedContent(aVerifySignedContent) + , mEnforceSRI(aEnforceSRI) + , mForceInheritPrincipalDropped(aForceInheritPrincipalDropped) + , mInnerWindowID(aInnerWindowID) + , mOuterWindowID(aOuterWindowID) + , mParentOuterWindowID(aParentOuterWindowID) + , mFrameOuterWindowID(aFrameOuterWindowID) + , mEnforceSecurity(aEnforceSecurity) + , mInitialSecurityCheckDone(aInitialSecurityCheckDone) + , mIsThirdPartyContext(aIsThirdPartyContext) + , mOriginAttributes(aOriginAttributes) + , mCorsUnsafeHeaders(aCorsUnsafeHeaders) + , mForcePreflight(aForcePreflight) + , mIsPreflight(aIsPreflight) + , mForceHSTSPriming (aForceHSTSPriming) + , mMixedContentWouldBlock(aMixedContentWouldBlock) +{ + // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal + MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT); + MOZ_ASSERT(mTriggeringPrincipal); + + mRedirectChainIncludingInternalRedirects.SwapElements( + aRedirectChainIncludingInternalRedirects); + + mRedirectChain.SwapElements(aRedirectChain); +} + +LoadInfo::~LoadInfo() +{ +} + +void +LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) +{ + nsContentPolicyType type = + nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType); + if (type == nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + nsCOMPtr util(do_GetService(THIRDPARTYUTIL_CONTRACTID)); + if (NS_WARN_IF(!util)) { + return; + } + + util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext); +} + +NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo) + +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->mEnforceSecurity = false; + copy->mInitialSecurityCheckDone = false; + copy->mRedirectChainIncludingInternalRedirects.Clear(); + copy->mRedirectChain.Clear(); + return copy.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) +{ + NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal); + return NS_OK; +} + +nsIPrincipal* +LoadInfo::LoadingPrincipal() +{ + return mLoadingPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) +{ + NS_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal); + return NS_OK; +} + +nsIPrincipal* +LoadInfo::TriggeringPrincipal() +{ + return mTriggeringPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) +{ + NS_IF_ADDREF(*aPrincipalToInherit = mPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) +{ + MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit"); + mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +nsIPrincipal* +LoadInfo::PrincipalToInherit() +{ + return mPrincipalToInherit; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingDocument(nsIDOMDocument** aResult) +{ + nsCOMPtr node = do_QueryReferent(mLoadingContext); + if (node) { + nsCOMPtr context = do_QueryInterface(node->OwnerDoc()); + context.forget(aResult); + } + return NS_OK; +} + +nsINode* +LoadInfo::LoadingNode() +{ + nsCOMPtr node = do_QueryReferent(mLoadingContext); + return node; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) +{ + *aResult = mSecurityFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityMode(uint32_t* aFlags) +{ + *aFlags = (mSecurityFlags & + (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL | + nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) +{ + *aIsInThirdPartyContext = mIsThirdPartyContext; + return NS_OK; +} + +static const uint32_t sCookiePolicyMask = + nsILoadInfo::SEC_COOKIES_DEFAULT | + nsILoadInfo::SEC_COOKIES_INCLUDE | + nsILoadInfo::SEC_COOKIES_SAME_ORIGIN | + nsILoadInfo::SEC_COOKIES_OMIT; + +NS_IMETHODIMP +LoadInfo::GetCookiePolicy(uint32_t *aResult) +{ + uint32_t policy = mSecurityFlags & sCookiePolicyMask; + if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) { + policy = (mSecurityFlags & SEC_REQUIRE_CORS_DATA_INHERITS) ? + nsILoadInfo::SEC_COOKIES_SAME_ORIGIN : nsILoadInfo::SEC_COOKIES_INCLUDE; + } + + *aResult = policy; + return NS_OK; +} + +void +LoadInfo::SetIncludeCookiesSecFlag() +{ + MOZ_ASSERT(!mEnforceSecurity, + "Request should not have been opened yet"); + MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) == + nsILoadInfo::SEC_COOKIES_DEFAULT); + mSecurityFlags = (mSecurityFlags & ~sCookiePolicyMask) | + nsILoadInfo::SEC_COOKIES_INCLUDE; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) +{ + *aInheritPrincipal = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) +{ + *aInheritPrincipal = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) +{ + *aLoadingSandboxed = (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAboutBlankInherits(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowChrome(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetDisallowScript(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT); + return NS_OK; +} + + +NS_IMETHODIMP +LoadInfo::GetDontFollowRedirects(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadErrorPage(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) +{ + *aResult = nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType); + return NS_OK; +} + +nsContentPolicyType +LoadInfo::InternalContentPolicyType() +{ + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +LoadInfo::GetUpgradeInsecureRequests(bool* aResult) +{ + *aResult = mUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetVerifySignedContent(bool aVerifySignedContent) +{ + MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only verify content for TYPE_DOCUMENT"); + mVerifySignedContent = aVerifySignedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetVerifySignedContent(bool* aResult) +{ + *aResult = mVerifySignedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetEnforceSRI(bool aEnforceSRI) +{ + mEnforceSRI = aEnforceSRI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetEnforceSRI(bool* aResult) +{ + *aResult = mEnforceSRI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalDropped(bool* aResult) +{ + *aResult = mForceInheritPrincipalDropped; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInnerWindowID(uint64_t* aResult) +{ + *aResult = mInnerWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetOuterWindowID(uint64_t* aResult) +{ + *aResult = mOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetParentOuterWindowID(uint64_t* aResult) +{ + *aResult = mParentOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameOuterWindowID(uint64_t* aResult) +{ + *aResult = mFrameOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetScriptableOriginAttributes(JSContext* aCx, + JS::MutableHandle aOriginAttributes) +{ + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::ResetPrincipalsToNullPrincipal() +{ + // take the originAttributes from the LoadInfo and create + // a new NullPrincipal using those origin attributes. + PrincipalOriginAttributes pAttrs; + pAttrs.InheritFromNecko(mOriginAttributes); + nsCOMPtr newNullPrincipal = nsNullPrincipal::Create(pAttrs); + + MOZ_ASSERT(mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT || + !mLoadingPrincipal, + "LoadingPrincipal should be null for toplevel loads"); + + // the loadingPrincipal for toplevel loads is always a nullptr; + if (mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) { + mLoadingPrincipal = newNullPrincipal; + } + mTriggeringPrincipal = newNullPrincipal; + mPrincipalToInherit = newNullPrincipal; + + // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule + // any non null owner set on the channel and will return the principal + // form the loadinfo instead. + mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER; + + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetScriptableOriginAttributes(JSContext* aCx, + JS::Handle aOriginAttributes) +{ + NeckoOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult +LoadInfo::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes) +{ + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult +LoadInfo::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes) +{ + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetEnforceSecurity(bool aEnforceSecurity) +{ + // Indicates whether the channel was openend using AsyncOpen2. Once set + // to true, it must remain true throughout the lifetime of the channel. + // Setting it to anything else than true will be discarded. + MOZ_ASSERT(aEnforceSecurity, "aEnforceSecurity must be true"); + mEnforceSecurity = mEnforceSecurity || aEnforceSecurity; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetEnforceSecurity(bool* aResult) +{ + *aResult = mEnforceSecurity; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) +{ + // Indicates whether the channel was ever evaluated by the + // ContentSecurityManager. Once set to true, this flag must + // remain true throughout the lifetime of the channel. + // Setting it to anything else than true will be discarded. + MOZ_ASSERT(aInitialSecurityCheckDone, "aInitialSecurityCheckDone must be true"); + mInitialSecurityCheckDone = mInitialSecurityCheckDone || aInitialSecurityCheckDone; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInitialSecurityCheckDone(bool* aResult) +{ + *aResult = mInitialSecurityCheckDone; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::AppendRedirectedPrincipal(nsIPrincipal* aPrincipal, bool aIsInternalRedirect) +{ + NS_ENSURE_ARG(aPrincipal); + MOZ_ASSERT(NS_IsMainThread()); + + mRedirectChainIncludingInternalRedirects.AppendElement(aPrincipal); + if (!aIsInternalRedirect) { + mRedirectChain.AppendElement(aPrincipal); + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChainIncludingInternalRedirects(JSContext* aCx, JS::MutableHandle aChain) +{ + if (!ToJSValue(aCx, mRedirectChainIncludingInternalRedirects, aChain)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +const nsTArray>& +LoadInfo::RedirectChainIncludingInternalRedirects() +{ + return mRedirectChainIncludingInternalRedirects; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChain(JSContext* aCx, JS::MutableHandle aChain) +{ + if (!ToJSValue(aCx, mRedirectChain, aChain)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +const nsTArray>& +LoadInfo::RedirectChain() +{ + return mRedirectChain; +} + +void +LoadInfo::SetCorsPreflightInfo(const nsTArray& aHeaders, + bool aForcePreflight) +{ + MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mCorsUnsafeHeaders = aHeaders; + 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_DATA_INHERITS); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mIsPreflight = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsPreflight(bool* aIsPreflight) +{ + *aIsPreflight = mIsPreflight; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceHSTSPriming(bool* aForceHSTSPriming) +{ + *aForceHSTSPriming = mForceHSTSPriming; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetMixedContentWouldBlock(bool *aMixedContentWouldBlock) +{ + *aMixedContentWouldBlock = mMixedContentWouldBlock; + return NS_OK; +} + +void +LoadInfo::SetHSTSPriming(bool aMixedContentWouldBlock) +{ + mForceHSTSPriming = true; + mMixedContentWouldBlock = aMixedContentWouldBlock; +} + +void +LoadInfo::ClearHSTSPriming() +{ + mForceHSTSPriming = false; + mMixedContentWouldBlock = false; +} + +NS_IMETHODIMP +LoadInfo::GetTainting(uint32_t* aTaintingOut) +{ + MOZ_ASSERT(aTaintingOut); + *aTaintingOut = static_cast(mTainting); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::MaybeIncreaseTainting(uint32_t aTainting) +{ + NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE); + LoadTainting tainting = static_cast(aTainting); + if (tainting > mTainting) { + mTainting = tainting; + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsTopLevelLoad(bool *aResult) +{ + *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID + : mParentOuterWindowID == mOuterWindowID; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h new file mode 100644 index 000000000..261f85349 --- /dev/null +++ b/netwerk/base/LoadInfo.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoadInfo_h +#define mozilla_LoadInfo_h + +#include "nsIContentPolicy.h" +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nsIURI.h" +#include "nsTArray.h" + +#include "mozilla/BasePrincipal.h" + +class nsINode; +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace dom { +class XMLHttpRequestMainThread; +} + +namespace net { +class OptionalLoadInfoArgs; +} // namespace net + +namespace ipc { +// we have to forward declare that function so we can use it as a friend. +nsresult +LoadInfoArgsToLoadInfo(const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs, + nsILoadInfo** outLoadInfo); +} // namespace ipc + +namespace net { + +/** + * Class that provides an nsILoadInfo implementation. + * + * Note that there is no reason why this class should be MOZ_EXPORT, but + * Thunderbird relies on some insane hacks which require this, so we'll leave it + * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword + * from this class at some point. See bug 1149127 for the discussion. + */ +class MOZ_EXPORT LoadInfo final : public nsILoadInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADINFO + + // aLoadingPrincipal MUST NOT BE NULL. + LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType); + + // Constructor used for TYPE_DOCUMENT loads which have no reasonable + // loadingNode or loadingPrincipal + LoadInfo(nsPIDOMWindowOuter* aOuterWindow, + nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags); + + // create an exact copy of the loadinfo + already_AddRefed 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; + + void SetIsPreflight(); + +private: + // private constructor that is only allowed to be called from within + // HttpChannelParent and FTPChannelParent declared as friends undeneath. + // In e10s we can not serialize nsINode, hence we store the innerWindowID. + // Please note that aRedirectChain uses swapElements. + LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, + bool aUpgradeInsecureRequests, + bool aVerifySignedContent, + bool aEnforceSRI, + bool aForceInheritPrincipalDropped, + uint64_t aInnerWindowID, + uint64_t aOuterWindowID, + uint64_t aParentOuterWindowID, + uint64_t aFrameOuterWindowID, + bool aEnforceSecurity, + bool aInitialSecurityCheckDone, + bool aIsThirdPartyRequest, + const NeckoOriginAttributes& aOriginAttributes, + nsTArray>& aRedirectChainIncludingInternalRedirects, + nsTArray>& aRedirectChain, + const nsTArray& aUnsafeHeaders, + bool aForcePreflight, + bool aIsPreflight, + bool aForceHSTSPriming, + bool aMixedContentWouldBlock); + LoadInfo(const LoadInfo& rhs); + + friend nsresult + mozilla::ipc::LoadInfoArgsToLoadInfo( + const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs, + nsILoadInfo** outLoadInfo); + + ~LoadInfo(); + + void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow); + + // This function is the *only* function which can change the securityflags + // of a loadinfo. It only exists because of the XHR code. Don't call it + // from anywhere else! + void SetIncludeCookiesSecFlag(); + friend class mozilla::dom::XMLHttpRequestMainThread; + + // if you add a member, please also update the copy constructor + nsCOMPtr mLoadingPrincipal; + nsCOMPtr mTriggeringPrincipal; + nsCOMPtr mPrincipalToInherit; + nsWeakPtr mLoadingContext; + nsSecurityFlags mSecurityFlags; + nsContentPolicyType mInternalContentPolicyType; + LoadTainting mTainting; + bool mUpgradeInsecureRequests; + bool mVerifySignedContent; + bool mEnforceSRI; + bool mForceInheritPrincipalDropped; + uint64_t mInnerWindowID; + uint64_t mOuterWindowID; + uint64_t mParentOuterWindowID; + uint64_t mFrameOuterWindowID; + bool mEnforceSecurity; + bool mInitialSecurityCheckDone; + bool mIsThirdPartyContext; + NeckoOriginAttributes mOriginAttributes; + nsTArray> mRedirectChainIncludingInternalRedirects; + nsTArray> mRedirectChain; + nsTArray mCorsUnsafeHeaders; + bool mForcePreflight; + bool mIsPreflight; + + bool mForceHSTSPriming : 1; + bool mMixedContentWouldBlock : 1; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_LoadInfo_h + diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h new file mode 100644 index 000000000..e2633969f --- /dev/null +++ b/netwerk/base/LoadTainting.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoadTainting_h +#define mozilla_LoadTainting_h + +namespace mozilla { + +// Define an enumeration to reflect the concept of response tainting from the +// the fetch spec: +// +// https://fetch.spec.whatwg.org/#concept-request-response-tainting +// +// Roughly the tainting means: +// +// * Basic: the request resulted in a same-origin or non-http load +// * CORS: the request resulted in a cross-origin load with CORS headers +// * Opaque: the request resulted in a cross-origin load without CORS headers +// +// The enumeration is purposefully designed such that more restrictive tainting +// corresponds to a higher integral value. +// +// NOTE: Checking the tainting is not currently adequate. You *must* still +// check the final URL and CORS mode on the channel. +// +// These values are currently only set on the channel LoadInfo when the request +// was initiated through fetch() or when a service worker interception occurs. +// In the future we should set the tainting value within necko so that it is +// consistently applied. Once that is done consumers can replace checks against +// the final URL and CORS mode with checks against tainting. +enum class LoadTainting : uint8_t +{ + Basic = 0, + CORS = 1, + Opaque = 2 +}; + +} // namespace mozilla + +#endif // mozilla_LoadTainting_h diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp new file mode 100644 index 000000000..f8add31aa --- /dev/null +++ b/netwerk/base/MemoryDownloader.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryDownloader.h" + +#include "mozilla/Assertions.h" +#include "nsIInputStream.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(MemoryDownloader, + nsIStreamListener, + nsIRequestObserver) + +MemoryDownloader::MemoryDownloader(IObserver* aObserver) +: mObserver(aObserver) +{ +} + +MemoryDownloader::~MemoryDownloader() +{ +} + +NS_IMETHODIMP +MemoryDownloader::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) +{ + MOZ_ASSERT(!mData); + mData.reset(new FallibleTArray()); + mStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnStopRequest(nsIRequest* aRequest, + nsISupports* aCtxt, + nsresult aStatus) +{ + MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus)); + MOZ_ASSERT(!mData == NS_FAILED(mStatus)); + Data data; + data.swap(mData); + RefPtr observer; + observer.swap(mObserver); + observer->OnDownloadComplete(this, aRequest, aCtxt, aStatus, + mozilla::Move(data)); + return NS_OK; +} + +nsresult +MemoryDownloader::ConsumeData(nsIInputStream* aIn, + void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + MemoryDownloader* self = static_cast(aClosure); + if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) { + // The error returned by ConsumeData isn't propagated to the + // return of ReadSegments, so it has to be passed as state. + self->mStatus = NS_ERROR_OUT_OF_MEMORY; + return NS_ERROR_OUT_OF_MEMORY; + } + *aWriteCount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aCtxt, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) +{ + uint32_t n; + MOZ_ASSERT(mData); + nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n); + if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) { + mStatus = rv; + } + if (NS_WARN_IF(NS_FAILED(mStatus))) { + mData.reset(nullptr); + return mStatus; + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h new file mode 100644 index 000000000..32fcff66c --- /dev/null +++ b/netwerk/base/MemoryDownloader.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_MemoryDownloader_h__ +#define mozilla_net_MemoryDownloader_h__ + +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsTArray.h" + +/** + * mozilla::net::MemoryDownloader + * + * This class is similar to nsIDownloader, but stores the downloaded + * stream in memory instead of a file. Ownership of the temporary + * memory is transferred to the observer when download is complete; + * there is no need to retain a reference to the downloader. + */ + +namespace mozilla { +namespace net { + +class MemoryDownloader final : public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + typedef mozilla::UniquePtr> Data; + + class IObserver : public nsISupports { + public: + // Note: aData may be null if (and only if) aStatus indicates failure. + virtual void OnDownloadComplete(MemoryDownloader* aDownloader, + nsIRequest* aRequest, + nsISupports* aCtxt, + nsresult aStatus, + Data aData) = 0; + }; + + explicit MemoryDownloader(IObserver* aObserver); + +private: + virtual ~MemoryDownloader(); + + static nsresult ConsumeData(nsIInputStream *in, + void *closure, + const char *fromRawSegment, + uint32_t toOffset, + uint32_t count, + uint32_t *writeCount); + + RefPtr mObserver; + Data mData; + nsresult mStatus; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_MemoryDownloader_h__ diff --git a/netwerk/base/NetStatistics.h b/netwerk/base/NetStatistics.h new file mode 100644 index 000000000..261355083 --- /dev/null +++ b/netwerk/base/NetStatistics.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetStatistics_h__ +#define NetStatistics_h__ + +#include "mozilla/Assertions.h" + +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsINetworkInterface.h" +#include "nsINetworkManager.h" +#include "nsINetworkStatsServiceProxy.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +namespace mozilla { +namespace net { + +// The following members are used for network per-app metering. +const static uint64_t NETWORK_STATS_THRESHOLD = 65536; +const static char NETWORK_STATS_NO_SERVICE_TYPE[] = ""; + +inline nsresult +GetActiveNetworkInfo(nsCOMPtr &aNetworkInfo) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr networkManager = + do_GetService("@mozilla.org/network/manager;1", &rv); + + if (NS_FAILED(rv) || !networkManager) { + aNetworkInfo = nullptr; + return rv; + } + + networkManager->GetActiveNetworkInfo(getter_AddRefs(aNetworkInfo)); + + return NS_OK; +} + +class SaveNetworkStatsEvent : public Runnable { +public: + SaveNetworkStatsEvent(uint32_t aAppId, + bool aIsInIsolatedMozBrowser, + nsMainThreadPtrHandle &aActiveNetworkInfo, + uint64_t aCountRecv, + uint64_t aCountSent, + bool aIsAccumulative) + : mAppId(aAppId), + mIsInIsolatedMozBrowser(aIsInIsolatedMozBrowser), + mActiveNetworkInfo(aActiveNetworkInfo), + mCountRecv(aCountRecv), + mCountSent(aCountSent), + mIsAccumulative(aIsAccumulative) + { + MOZ_ASSERT(mAppId != NECKO_NO_APP_ID); + MOZ_ASSERT(mActiveNetworkInfo); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr mNetworkStatsServiceProxy = + do_GetService("@mozilla.org/networkstatsServiceProxy;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // save the network stats through NetworkStatsServiceProxy + mNetworkStatsServiceProxy->SaveAppStats(mAppId, + mIsInIsolatedMozBrowser, + mActiveNetworkInfo, + PR_Now() / 1000, + mCountRecv, + mCountSent, + mIsAccumulative, + nullptr); + + return NS_OK; + } +private: + uint32_t mAppId; + bool mIsInIsolatedMozBrowser; + nsMainThreadPtrHandle mActiveNetworkInfo; + uint64_t mCountRecv; + uint64_t mCountSent; + bool mIsAccumulative; +}; + +} // namespace mozilla:net +} // namespace mozilla + +#endif // !NetStatistics_h__ diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm new file mode 100644 index 000000000..e970c8ad8 --- /dev/null +++ b/netwerk/base/NetUtil.jsm @@ -0,0 +1,461 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.EXPORTED_SYMBOLS = [ + "NetUtil", +]; + +/** + * Necko utilities + */ + +//////////////////////////////////////////////////////////////////////////////// +//// Constants + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cr = Components.results; +const Cu = Components.utils; + +const PR_UINT32_MAX = 0xffffffff; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +//////////////////////////////////////////////////////////////////////////////// +//// NetUtil Object + +this.NetUtil = { + /** + * Function to perform simple async copying from aSource (an input stream) + * to aSink (an output stream). The copy will happen on some background + * thread. Both streams will be closed when the copy completes. + * + * @param aSource + * The input stream to read from + * @param aSink + * The output stream to write to + * @param aCallback [optional] + * A function that will be called at copy completion with a single + * argument: the nsresult status code for the copy operation. + * + * @return An nsIRequest representing the copy operation (for example, this + * can be used to cancel the copying). The consumer can ignore the + * return value if desired. + */ + asyncCopy: function NetUtil_asyncCopy(aSource, aSink, + aCallback = null) + { + if (!aSource || !aSink) { + let exception = new Components.Exception( + "Must have a source and a sink", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // make a stream copier + var copier = Cc["@mozilla.org/network/async-stream-copier;1"]. + createInstance(Ci.nsIAsyncStreamCopier2); + copier.init(aSource, aSink, + null /* Default event target */, + 0 /* Default length */, + true, true /* Auto-close */); + + var observer; + if (aCallback) { + observer = { + onStartRequest: function(aRequest, aContext) {}, + onStopRequest: function(aRequest, aContext, aStatusCode) { + aCallback(aStatusCode); + } + } + } else { + observer = null; + } + + // start the copying + copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null); + return copier; + }, + + /** + * Asynchronously opens a source and fetches the response. While the fetch + * is asynchronous, I/O may happen on the main thread. When reading from + * a local file, prefer using "OS.File" methods instead. + * + * @param aSource + * This argument can be one of the following: + * - An options object that will be passed to NetUtil.newChannel. + * - An existing nsIChannel. + * - An existing nsIInputStream. + * Using an nsIURI, nsIFile, or string spec directly is deprecated. + * @param aCallback + * The callback function that will be notified upon completion. It + * will get these arguments: + * 1) An nsIInputStream containing the data from aSource, if any. + * 2) The status code from opening the source. + * 3) Reference to the nsIRequest. + */ + asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) + { + if (!aSource || !aCallback) { + let exception = new Components.Exception( + "Must have a source and a callback", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // Create a pipe that will create our output stream that we can use once + // we have gotten all the data. + let pipe = Cc["@mozilla.org/pipe;1"]. + createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, PR_UINT32_MAX, null); + + // Create a listener that will give data to the pipe's output stream. + let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]. + createInstance(Ci.nsISimpleStreamListener); + listener.init(pipe.outputStream, { + onStartRequest: function(aRequest, aContext) {}, + onStopRequest: function(aRequest, aContext, aStatusCode) { + pipe.outputStream.close(); + aCallback(pipe.inputStream, aStatusCode, aRequest); + } + }); + + // Input streams are handled slightly differently from everything else. + if (aSource instanceof Ci.nsIInputStream) { + let pump = Cc["@mozilla.org/network/input-stream-pump;1"]. + createInstance(Ci.nsIInputStreamPump); + pump.init(aSource, -1, -1, 0, 0, true); + pump.asyncRead(listener, null); + return; + } + + let channel = aSource; + if (!(channel instanceof Ci.nsIChannel)) { + channel = this.newChannel(aSource); + } + + try { + // Open the channel using asyncOpen2() if the loadinfo contains one + // of the security mode flags, otherwise fall back to use asyncOpen(). + if (channel.loadInfo && + channel.loadInfo.securityMode != 0) { + channel.asyncOpen2(listener); + } + else { + // Log deprecation warning to console to make sure all channels + // are created providing the correct security flags in the loadinfo. + // See nsILoadInfo for all available security flags and also the API + // of NetUtil.newChannel() for details above. + Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " + + "one of the security flags set in the loadinfo (see nsILoadInfo). " + + "Please create channel using NetUtil.newChannel()"); + channel.asyncOpen(listener, null); + } + } + catch (e) { + let exception = new Components.Exception( + "Failed to open input source '" + channel.originalURI.spec + "'", + e.result, + Components.stack.caller, + aSource, + e + ); + throw exception; + } + }, + + /** + * Constructs a new URI for the given spec, character set, and base URI, or + * an nsIFile. + * + * @param aTarget + * The string spec for the desired URI or an nsIFile. + * @param aOriginCharset [optional] + * The character set for the URI. Only used if aTarget is not an + * nsIFile. + * @param aBaseURI [optional] + * The base URI for the spec. Only used if aTarget is not an + * nsIFile. + * + * @return an nsIURI object. + */ + newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) + { + if (!aTarget) { + let exception = new Components.Exception( + "Must have a non-null string spec or nsIFile object", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aTarget instanceof Ci.nsIFile) { + return this.ioService.newFileURI(aTarget); + } + + return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI); + }, + + /** + * Constructs a new channel for the given source. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + * + * @param aWhatToLoad + * This argument used to be a string spec for the desired URI, an + * nsIURI, or an nsIFile. Now it should be an options object with + * the following properties: + * { + * uri: + * The full URI spec string, nsIURI or nsIFile to create the + * channel for. + * Note that this cannot be an nsIFile if you have to specify a + * non-default charset or base URI. Call NetUtil.newURI first if + * you need to construct an URI using those options. + * loadingNode: + * loadingPrincipal: + * triggeringPrincipal: + * securityFlags: + * contentPolicyType: + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * loadUsingSystemPrincipal: + * Set this to true to use the system principal as + * loadingPrincipal. This must be omitted if loadingPrincipal or + * loadingNode are present. + * This should be used with care as it skips security checks. + * } + * @param aOriginCharset [deprecated] + * The character set for the URI. Only used if aWhatToLoad is a + * string, which is a deprecated API. Must be undefined otherwise. + * Use NetUtil.newURI if you need to use this option. + * @param aBaseURI [deprecated] + * The base URI for the spec. Only used if aWhatToLoad is a string, + * which is a deprecated API. Must be undefined otherwise. Use + * NetUtil.newURI if you need to use this option. + * @return an nsIChannel object. + */ + newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI) + { + // Check for the deprecated API first. + if (typeof aWhatToLoad == "string" || + (aWhatToLoad instanceof Ci.nsIFile) || + (aWhatToLoad instanceof Ci.nsIURI)) { + + let uri = (aWhatToLoad instanceof Ci.nsIURI) + ? aWhatToLoad + : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); + + // log deprecation warning for developers. + Services.console.logStringMessage( + "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'"); + + // Provide default loadinfo arguments and call the new API. + let systemPrincipal = + Services.scriptSecurityManager.getSystemPrincipal(); + + return this.ioService.newChannelFromURI2( + uri, + null, // loadingNode + systemPrincipal, // loadingPrincipal + null, // triggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + } + + // We are using the updated API, that requires only the options object. + if (typeof aWhatToLoad != "object" || + aOriginCharset !== undefined || + aBaseURI !== undefined) { + throw new Components.Exception( + "newChannel requires a single object argument", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + let { uri, + loadingNode, + loadingPrincipal, + loadUsingSystemPrincipal, + triggeringPrincipal, + securityFlags, + contentPolicyType } = aWhatToLoad; + + if (!uri) { + throw new Components.Exception( + "newChannel requires the 'uri' property on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (typeof uri == "string" || uri instanceof Ci.nsIFile) { + uri = this.newURI(uri); + } + + if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires at least one of the 'loadingNode'," + + " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" + + " properties on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (loadUsingSystemPrincipal === true) { + if (loadingNode || loadingPrincipal) { + throw new Components.Exception( + "newChannel does not accept 'loadUsingSystemPrincipal'" + + " if the 'loadingNode' or 'loadingPrincipal' properties" + + " are present on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + loadingPrincipal = Services.scriptSecurityManager + .getSystemPrincipal(); + } else if (loadUsingSystemPrincipal !== undefined) { + throw new Components.Exception( + "newChannel requires the 'loadUsingSystemPrincipal'" + + " property on the options object to be 'true' or 'undefined'.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (securityFlags === undefined) { + securityFlags = loadUsingSystemPrincipal + ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL + : Ci.nsILoadInfo.SEC_NORMAL; + } + + if (contentPolicyType === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'contentPolicyType' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; + } + + return this.ioService.newChannelFromURI2(uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType); + }, + + /** + * Reads aCount bytes from aInputStream into a string. + * + * @param aInputStream + * The input stream to read from. + * @param aCount + * The number of bytes to read from the stream. + * @param aOptions [optional] + * charset + * The character encoding of stream data. + * replacement + * The character to replace unknown byte sequences. + * If unset, it causes an exceptions to be thrown. + * + * @return the bytes from the input stream in string form. + * + * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would + * block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences + */ + readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream, + aCount, + aOptions) + { + if (!(aInputStream instanceof Ci.nsIInputStream)) { + let exception = new Components.Exception( + "First argument should be an nsIInputStream", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (!aCount) { + let exception = new Components.Exception( + "Non-zero amount of bytes must be specified", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aOptions && "charset" in aOptions) { + let cis = Cc["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Ci.nsIConverterInputStream); + try { + // When replacement is set, the character that is unknown sequence + // replaces with aOptions.replacement character. + if (!("replacement" in aOptions)) { + // aOptions.replacement isn't set. + // If input stream has unknown sequences for aOptions.charset, + // throw NS_ERROR_ILLEGAL_INPUT. + aOptions.replacement = 0; + } + + cis.init(aInputStream, aOptions.charset, aCount, + aOptions.replacement); + let str = {}; + cis.readString(-1, str); + cis.close(); + return str.value; + } + catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception(e.message, e.result, + Components.stack.caller, e.data); + } + } + + let sis = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + sis.init(aInputStream); + try { + return sis.readBytes(aCount); + } + catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception(e.message, e.result, + Components.stack.caller, e.data); + } + }, + + /** + * Returns a reference to nsIIOService. + * + * @return a reference to nsIIOService. + */ + get ioService() + { + delete this.ioService; + return this.ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + }, +}; diff --git a/netwerk/base/NetworkActivityMonitor.cpp b/netwerk/base/NetworkActivityMonitor.cpp new file mode 100644 index 000000000..887878977 --- /dev/null +++ b/netwerk/base/NetworkActivityMonitor.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NetworkActivityMonitor.h" +#include "prmem.h" +#include "nsIObserverService.h" +#include "nsPISocketTransportService.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" +#include "prerror.h" + +using namespace mozilla::net; + +static PRStatus +nsNetMon_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout) +{ + PRStatus ret; + PRErrorCode code; + ret = fd->lower->methods->connect(fd->lower, addr, timeout); + if (ret == PR_SUCCESS || (code = PR_GetError()) == PR_WOULD_BLOCK_ERROR || + code == PR_IN_PROGRESS_ERROR) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Read(PRFileDesc *fd, void *buf, int32_t len) +{ + int32_t ret; + ret = fd->lower->methods->read(fd->lower, buf, len); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + +static int32_t +nsNetMon_Write(PRFileDesc *fd, const void *buf, int32_t len) +{ + int32_t ret; + ret = fd->lower->methods->write(fd->lower, buf, len); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Writev(PRFileDesc *fd, + const PRIOVec *iov, + int32_t size, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->writev(fd->lower, iov, size, timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Recv(PRFileDesc *fd, + void *buf, + int32_t amount, + int flags, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + +static int32_t +nsNetMon_Send(PRFileDesc *fd, + const void *buf, + int32_t amount, + int flags, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_RecvFrom(PRFileDesc *fd, + void *buf, + int32_t amount, + int flags, + PRNetAddr *addr, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->recvfrom(fd->lower, + buf, + amount, + flags, + addr, + timeout); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + +static int32_t +nsNetMon_SendTo(PRFileDesc *fd, + const void *buf, + int32_t amount, + int flags, + const PRNetAddr *addr, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->sendto(fd->lower, + buf, + amount, + flags, + addr, + timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_AcceptRead(PRFileDesc *listenSock, + PRFileDesc **acceptedSock, + PRNetAddr **peerAddr, + void *buf, + int32_t amount, + PRIntervalTime timeout) +{ + int32_t ret; + ret = listenSock->lower->methods->acceptread(listenSock->lower, + acceptedSock, + peerAddr, + buf, + amount, + timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + + +class NotifyNetworkActivity : public mozilla::Runnable { +public: + explicit NotifyNetworkActivity(NetworkActivityMonitor::Direction aDirection) + : mDirection(aDirection) + {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) + return NS_ERROR_FAILURE; + + obs->NotifyObservers(nullptr, + mDirection == NetworkActivityMonitor::kUpload + ? NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC + : NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC, + nullptr); + return NS_OK; + } +private: + NetworkActivityMonitor::Direction mDirection; +}; + +NetworkActivityMonitor * NetworkActivityMonitor::gInstance = nullptr; +static PRDescIdentity sNetActivityMonitorLayerIdentity; +static PRIOMethods sNetActivityMonitorLayerMethods; +static PRIOMethods *sNetActivityMonitorLayerMethodsPtr = nullptr; + +NetworkActivityMonitor::NetworkActivityMonitor() + : mBlipInterval(PR_INTERVAL_NO_TIMEOUT) +{ + MOZ_COUNT_CTOR(NetworkActivityMonitor); + + NS_ASSERTION(gInstance==nullptr, + "multiple NetworkActivityMonitor instances!"); +} + +NetworkActivityMonitor::~NetworkActivityMonitor() +{ + MOZ_COUNT_DTOR(NetworkActivityMonitor); + gInstance = nullptr; +} + +nsresult +NetworkActivityMonitor::Init(int32_t blipInterval) +{ + nsresult rv; + + if (gInstance) + return NS_ERROR_ALREADY_INITIALIZED; + + NetworkActivityMonitor * mon = new NetworkActivityMonitor(); + rv = mon->Init_Internal(blipInterval); + if (NS_FAILED(rv)) { + delete mon; + return rv; + } + + gInstance = mon; + return NS_OK; +} + +nsresult +NetworkActivityMonitor::Shutdown() +{ + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + delete gInstance; + return NS_OK; +} + +nsresult +NetworkActivityMonitor::Init_Internal(int32_t blipInterval) +{ + if (!sNetActivityMonitorLayerMethodsPtr) { + sNetActivityMonitorLayerIdentity = + PR_GetUniqueIdentity("network activity monitor layer"); + sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods(); + sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect; + sNetActivityMonitorLayerMethods.read = nsNetMon_Read; + sNetActivityMonitorLayerMethods.write = nsNetMon_Write; + sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev; + sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv; + sNetActivityMonitorLayerMethods.send = nsNetMon_Send; + sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom; + sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo; + sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead; + sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods; + } + + mBlipInterval = PR_MillisecondsToInterval(blipInterval); + // Set the last notification times to time that has just expired, so any + // activity even right now will trigger notification. + mLastNotificationTime[kUpload] = PR_IntervalNow() - mBlipInterval; + mLastNotificationTime[kDownload] = mLastNotificationTime[kUpload]; + + return NS_OK; +} + +nsresult +NetworkActivityMonitor::AttachIOLayer(PRFileDesc *fd) +{ + if (!gInstance) + return NS_OK; + + PRFileDesc * layer; + PRStatus status; + + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_DELETE(layer); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +NetworkActivityMonitor::DataInOut(Direction direction) +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (gInstance) { + PRIntervalTime now = PR_IntervalNow(); + if ((now - gInstance->mLastNotificationTime[direction]) > + gInstance->mBlipInterval) { + gInstance->mLastNotificationTime[direction] = now; + gInstance->PostNotification(direction); + } + } + + return NS_OK; +} + +void +NetworkActivityMonitor::PostNotification(Direction direction) +{ + nsCOMPtr ev = new NotifyNetworkActivity(direction); + NS_DispatchToMainThread(ev); +} diff --git a/netwerk/base/NetworkActivityMonitor.h b/netwerk/base/NetworkActivityMonitor.h new file mode 100644 index 000000000..28c9a911e --- /dev/null +++ b/netwerk/base/NetworkActivityMonitor.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetworkActivityMonitor_h___ +#define NetworkActivityMonitor_h___ + +#include +#include "nscore.h" +#include "prio.h" +#include "prinrval.h" + +namespace mozilla { namespace net { + +class NetworkActivityMonitor +{ +public: + enum Direction { + kUpload = 0, + kDownload = 1 + }; + + NetworkActivityMonitor(); + ~NetworkActivityMonitor(); + + static nsresult Init(int32_t blipInterval); + static nsresult Shutdown(); + + static nsresult AttachIOLayer(PRFileDesc *fd); + static nsresult DataInOut(Direction direction); + +private: + nsresult Init_Internal(int32_t blipInterval); + void PostNotification(Direction direction); + + static NetworkActivityMonitor * gInstance; + PRIntervalTime mBlipInterval; + PRIntervalTime mLastNotificationTime[2]; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NetworkActivityMonitor_h___ */ diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp new file mode 100644 index 000000000..937c72658 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceCocoa.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#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), 0, 0, NI_NUMERICHOST); + break; + case AF_UNSPEC: + return NS_OK; + default: + // Unknown family. + return NS_OK; + } + + nsCString ifaceStr; + ifaceStr.AssignASCII(aInterface); + + nsCString addrStr; + addrStr.AssignASCII(host); + + aAddrMap.Put(ifaceStr, addrStr); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h new file mode 100644 index 000000000..6f92c335f --- /dev/null +++ b/netwerk/base/NetworkInfoServiceImpl.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsDataHashtable.h" + +namespace mozilla { +namespace net { + +typedef nsDataHashtable AddrMapType; + +nsresult DoListAddresses(AddrMapType& aAddrMap); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceLinux.cpp b/netwerk/base/NetworkInfoServiceLinux.cpp new file mode 100644 index 000000000..96627cfec --- /dev/null +++ b/netwerk/base/NetworkInfoServiceLinux.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#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 = 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; + } + + autoCloseSocket.release(); + return NS_OK; +} + +static nsresult +ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap) +{ + struct ifreq ifreq; + memset(&ifreq, 0, sizeof(struct ifreq)); + strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1); + if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) { + return NS_ERROR_FAILURE; + } + + char host[128]; + int family; + switch(family=ifreq.ifr_addr.sa_family) { + case AF_INET: + case AF_INET6: + getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST); + break; + case AF_UNSPEC: + return NS_OK; + default: + // Unknown family. + return NS_OK; + } + + nsCString ifaceStr; + ifaceStr.AssignASCII(aInterface); + + nsCString addrStr; + addrStr.AssignASCII(host); + + aAddrMap.Put(ifaceStr, addrStr); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp new file mode 100644 index 000000000..2a9448e35 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceWindows.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#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.Put(indexString, addrString); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp new file mode 100644 index 000000000..9cb45efde --- /dev/null +++ b/netwerk/base/PollableEvent.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransportService2.h" +#include "PollableEvent.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "prerror.h" +#include "prio.h" +#include "private/pprio.h" +#include "prnetdb.h" + +#ifdef XP_WIN +#include "ShutdownLayer.h" +#else +#include +#define USEPIPE 1 +#endif + +namespace mozilla { +namespace net { + +#ifndef USEPIPE +static PRDescIdentity sPollableEventLayerIdentity; +static PRIOMethods sPollableEventLayerMethods; +static PRIOMethods *sPollableEventLayerMethodsPtr = nullptr; + +static void LazyInitSocket() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (sPollableEventLayerMethodsPtr) { + return; + } + sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer"); + sPollableEventLayerMethods = *PR_GetDefaultIOMethods(); + sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods; +} + +static bool NewTCPSocketPair(PRFileDesc *fd[], bool aSetRecvBuff) +{ + // this is a replacement for PR_NewTCPSocketPair that manually + // sets the recv buffer to 64K. A windows bug (1248358) + // can result in using an incompatible rwin and window + // scale option on localhost pipes if not set before connect. + + SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", aSetRecvBuff ? "with" : "without")); + + PRFileDesc *listener = nullptr; + PRFileDesc *writer = nullptr; + PRFileDesc *reader = nullptr; + PRSocketOptionData recvBufferOpt; + recvBufferOpt.option = PR_SockOpt_RecvBufferSize; + recvBufferOpt.value.recv_buffer_size = 65535; + + PRSocketOptionData nodelayOpt; + nodelayOpt.option = PR_SockOpt_NoDelay; + nodelayOpt.value.no_delay = true; + + PRSocketOptionData noblockOpt; + noblockOpt.option = PR_SockOpt_Nonblocking; + noblockOpt.value.non_blocking = true; + + listener = PR_OpenTCPSocket(PR_AF_INET); + if (!listener) { + goto failed; + } + + if (aSetRecvBuff) { + PR_SetSocketOption(listener, &recvBufferOpt); + } + PR_SetSocketOption(listener, &nodelayOpt); + + PRNetAddr listenAddr; + memset(&listenAddr, 0, sizeof(listenAddr)); + if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) || + (PR_Bind(listener, &listenAddr) == PR_FAILURE) || + (PR_GetSockName(listener, &listenAddr) == PR_FAILURE) || // learn the dynamic port + (PR_Listen(listener, 5) == PR_FAILURE)) { + goto failed; + } + + writer = PR_OpenTCPSocket(PR_AF_INET); + if (!writer) { + goto failed; + } + if (aSetRecvBuff) { + PR_SetSocketOption(writer, &recvBufferOpt); + } + PR_SetSocketOption(writer, &nodelayOpt); + PR_SetSocketOption(writer, &noblockOpt); + PRNetAddr writerAddr; + if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), &writerAddr) == PR_FAILURE) { + goto failed; + } + + if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) { + if ((PR_GetError() != PR_IN_PROGRESS_ERROR) || + (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) { + goto failed; + } + } + + reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); + if (!reader) { + goto failed; + } + if (aSetRecvBuff) { + PR_SetSocketOption(reader, &recvBufferOpt); + } + PR_SetSocketOption(reader, &nodelayOpt); + PR_SetSocketOption(reader, &noblockOpt); + PR_Close(listener); + + fd[0] = reader; + fd[1] = writer; + return true; + +failed: + if (listener) { + PR_Close(listener); + } + if (reader) { + PR_Close(reader); + } + if (writer) { + PR_Close(writer); + } + return false; +} + +#endif + +PollableEvent::PollableEvent() + : mWriteFD(nullptr) + , mReadFD(nullptr) + , mSignaled(false) +{ + MOZ_COUNT_CTOR(PollableEvent); + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + // create pair of prfiledesc that can be used as a poll()ble + // signal. on windows use a localhost socket pair, and on + // unix use a pipe. +#ifdef USEPIPE + SOCKET_LOG(("PollableEvent() using pipe\n")); + if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) { + // make the pipe non blocking. NSPR asserts at + // trying to use SockOpt here + PROsfd fd = PR_FileDesc2NativeHandle(mReadFD); + int flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + fd = PR_FileDesc2NativeHandle(mWriteFD); + flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } else { + mReadFD = nullptr; + mWriteFD = nullptr; + SOCKET_LOG(("PollableEvent() pipe failed\n")); + } +#else + SOCKET_LOG(("PollableEvent() using socket pair\n")); + PRFileDesc *fd[2]; + LazyInitSocket(); + + // Try with a increased recv buffer first (bug 1248358). + if (NewTCPSocketPair(fd, true)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If the previous fails try without recv buffer increase (bug 1305436). + } else if (NewTCPSocketPair(fd, false)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If both fail, try the old version. + } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + + PRSocketOptionData socket_opt; + DebugOnly status; + socket_opt.option = PR_SockOpt_NoDelay; + socket_opt.value.no_delay = true; + PR_SetSocketOption(mWriteFD, &socket_opt); + PR_SetSocketOption(mReadFD, &socket_opt); + socket_opt.option = PR_SockOpt_Nonblocking; + socket_opt.value.non_blocking = true; + status = PR_SetSocketOption(mWriteFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + status = PR_SetSocketOption(mReadFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + } + + if (mReadFD && mWriteFD) { + // compatibility with LSPs such as McAfee that assume a NSPR + // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop. + PRFileDesc *topLayer = + PR_CreateIOLayerStub(sPollableEventLayerIdentity, + sPollableEventLayerMethodsPtr); + if (topLayer) { + if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) { + topLayer->dtor(topLayer); + } else { + SOCKET_LOG(("PollableEvent() nspr layer ok\n")); + mReadFD = topLayer; + } + } + + } else { + SOCKET_LOG(("PollableEvent() socketpair failed\n")); + } +#endif + + if (mReadFD && mWriteFD) { + // prime the system to deal with races invovled in [dc]tor cycle + SOCKET_LOG(("PollableEvent() ctor ok\n")); + mSignaled = true; + PR_Write(mWriteFD, "I", 1); + } +} + +PollableEvent::~PollableEvent() +{ + MOZ_COUNT_DTOR(PollableEvent); + if (mWriteFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mWriteFD); +#endif + PR_Close(mWriteFD); + } + if (mReadFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mReadFD); +#endif + PR_Close(mReadFD); + } +} + +// we do not record signals on the socket thread +// because the socket thread can reliably look at its +// own runnable queue before selecting a poll time +// this is the "service the network without blocking" comment in +// nsSocketTransportService2.cpp +bool +PollableEvent::Signal() +{ + SOCKET_LOG(("PollableEvent::Signal\n")); + + if (!mWriteFD) { + SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n")); + return false; + } +#ifndef XP_WIN + // On windows poll can hang and this became worse when we introduced the + // patch for bug 698882 (see also bug 1292181), therefore we reverted the + // behavior on windows to be as before bug 698882, e.g. write to the socket + // also if an event dispatch is on the socket thread and writing to the + // socket for each event. See bug 1292181. + if (PR_GetCurrentThread() == gSocketThread) { + SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n")); + return true; + } +#endif + +#ifndef XP_WIN + // To wake up the poll writing once is enough, but for Windows that can cause + // hangs so we will write for every event. + // For non-Windows systems it is enough to write just once. + if (mSignaled) { + return true; + } +#endif + + mSignaled = true; + int32_t status = PR_Write(mWriteFD, "M", 1); + SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status)); + if (status != 1) { + NS_WARNING("PollableEvent::Signal Failed\n"); + SOCKET_LOG(("PollableEvent::Signal Failed\n")); + mSignaled = false; + } + return (status == 1); +} + +bool +PollableEvent::Clear() +{ + // necessary because of the "dont signal on socket thread" optimization + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + SOCKET_LOG(("PollableEvent::Clear\n")); + mSignaled = false; + if (!mReadFD) { + SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n")); + return false; + } + char buf[2048]; + int32_t status; +#ifdef XP_WIN + // On Windows we are writing to the socket for each event, to be sure that we + // do not have any deadlock read from the socket as much as we can. + while (true) { + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status)); + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } else { + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; + } + } + } +#else + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status)); + + if (status == 1) { + return true; + } + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status > 1) { + MOZ_ASSERT(false); + SOCKET_LOG(("PollableEvent::Clear Unexpected events\n")); + Clear(); + return true; + } + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; +#endif //XP_WIN + +} +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h new file mode 100644 index 000000000..800079932 --- /dev/null +++ b/netwerk/base/PollableEvent.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PollableEvent_h__ +#define PollableEvent_h__ + +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +// class must be called locked +class PollableEvent +{ +public: + PollableEvent(); + ~PollableEvent(); + + // Signal/Clear return false only if they fail + bool Signal(); + bool Clear(); + bool Valid() { return mWriteFD && mReadFD; } + + PRFileDesc *PollableFD() { return mReadFD; } + +private: + PRFileDesc *mWriteFD; + PRFileDesc *mReadFD; + bool mSignaled; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp new file mode 100644 index 000000000..e97b11d16 --- /dev/null +++ b/netwerk/base/Predictor.cpp @@ -0,0 +1,2550 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#include "Predictor.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsICacheStorage.h" +#include "nsICacheStorageService.h" +#include "nsICachingChannel.h" +#include "nsICancelable.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsIDNSService.h" +#include "nsIDocument.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsIInputStream.h" +#include "nsIIOService.h" +#include "nsILoadContext.h" +#include "nsILoadContextInfo.h" +#include "nsILoadGroup.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsISpeculativeConnect.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "mozilla/Logging.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/NeckoParent.h" + +#include "LoadContextInfo.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "mozilla/net/NeckoChild.h" + +#include "mozilla/dom/ContentParent.h" + +using namespace mozilla; + +namespace mozilla { +namespace net { + +Predictor *Predictor::sSelf = nullptr; + +static LazyLogModule gPredictorLog("NetworkPredictor"); + +#define PREDICTOR_LOG(args) MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args) + +#define RETURN_IF_FAILED(_rv) \ + do { \ + if (NS_FAILED(_rv)) { \ + return; \ + } \ + } while (0) + +#define NOW_IN_SECONDS() static_cast(PR_Now() / PR_USEC_PER_SEC) + + +static const char PREDICTOR_ENABLED_PREF[] = "network.predictor.enabled"; +static const char PREDICTOR_SSL_HOVER_PREF[] = "network.predictor.enable-hover-on-ssl"; +static const char PREDICTOR_PREFETCH_PREF[] = "network.predictor.enable-prefetch"; + +static const char PREDICTOR_PAGE_DELTA_DAY_PREF[] = + "network.predictor.page-degradation.day"; +static const int32_t PREDICTOR_PAGE_DELTA_DAY_DEFAULT = 0; +static const char PREDICTOR_PAGE_DELTA_WEEK_PREF[] = + "network.predictor.page-degradation.week"; +static const int32_t PREDICTOR_PAGE_DELTA_WEEK_DEFAULT = 5; +static const char PREDICTOR_PAGE_DELTA_MONTH_PREF[] = + "network.predictor.page-degradation.month"; +static const int32_t PREDICTOR_PAGE_DELTA_MONTH_DEFAULT = 10; +static const char PREDICTOR_PAGE_DELTA_YEAR_PREF[] = + "network.predictor.page-degradation.year"; +static const int32_t PREDICTOR_PAGE_DELTA_YEAR_DEFAULT = 25; +static const char PREDICTOR_PAGE_DELTA_MAX_PREF[] = + "network.predictor.page-degradation.max"; +static const int32_t PREDICTOR_PAGE_DELTA_MAX_DEFAULT = 50; +static const char PREDICTOR_SUB_DELTA_DAY_PREF[] = + "network.predictor.subresource-degradation.day"; +static const int32_t PREDICTOR_SUB_DELTA_DAY_DEFAULT = 1; +static const char PREDICTOR_SUB_DELTA_WEEK_PREF[] = + "network.predictor.subresource-degradation.week"; +static const int32_t PREDICTOR_SUB_DELTA_WEEK_DEFAULT = 10; +static const char PREDICTOR_SUB_DELTA_MONTH_PREF[] = + "network.predictor.subresource-degradation.month"; +static const int32_t PREDICTOR_SUB_DELTA_MONTH_DEFAULT = 25; +static const char PREDICTOR_SUB_DELTA_YEAR_PREF[] = + "network.predictor.subresource-degradation.year"; +static const int32_t PREDICTOR_SUB_DELTA_YEAR_DEFAULT = 50; +static const char PREDICTOR_SUB_DELTA_MAX_PREF[] = + "network.predictor.subresource-degradation.max"; +static const int32_t PREDICTOR_SUB_DELTA_MAX_DEFAULT = 100; + +static const char PREDICTOR_PREFETCH_ROLLING_LOAD_PREF[] = + "network.predictor.prefetch-rolling-load-count"; +static const int32_t PREFETCH_ROLLING_LOAD_DEFAULT = 10; +static const char PREDICTOR_PREFETCH_MIN_PREF[] = + "network.predictor.prefetch-min-confidence"; +static const int32_t PREFETCH_MIN_DEFAULT = 100; +static const char PREDICTOR_PRECONNECT_MIN_PREF[] = + "network.predictor.preconnect-min-confidence"; +static const int32_t PRECONNECT_MIN_DEFAULT = 90; +static const char PREDICTOR_PRERESOLVE_MIN_PREF[] = + "network.predictor.preresolve-min-confidence"; +static const int32_t PRERESOLVE_MIN_DEFAULT = 60; +static const char PREDICTOR_REDIRECT_LIKELY_PREF[] = + "network.predictor.redirect-likely-confidence"; +static const int32_t REDIRECT_LIKELY_DEFAULT = 75; + +static const char PREDICTOR_PREFETCH_FORCE_VALID_PREF[] = + "network.predictor.prefetch-force-valid-for"; +static const int32_t PREFETCH_FORCE_VALID_DEFAULT = 10; + +static const char PREDICTOR_MAX_RESOURCES_PREF[] = + "network.predictor.max-resources-per-entry"; +static const uint32_t PREDICTOR_MAX_RESOURCES_DEFAULT = 100; + +// This is selected in concert with max-resources-per-entry to keep memory usage +// low-ish. The default of the combo of the two is ~50k +static const char PREDICTOR_MAX_URI_LENGTH_PREF[] = + "network.predictor.max-uri-length"; +static const uint32_t PREDICTOR_MAX_URI_LENGTH_DEFAULT = 500; + +static const char PREDICTOR_DOING_TESTS_PREF[] = "network.predictor.doing-tests"; + +static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up"; + +// All these time values are in sec +static const uint32_t ONE_DAY = 86400U; +static const uint32_t ONE_WEEK = 7U * ONE_DAY; +static const uint32_t ONE_MONTH = 30U * ONE_DAY; +static const uint32_t ONE_YEAR = 365U * ONE_DAY; + +static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min + +// Version of metadata entries we expect +static const uint32_t METADATA_VERSION = 1; + +// Flags available in entries +// FLAG_PREFETCHABLE - we have determined that this item is eligible for prefetch +static const uint32_t FLAG_PREFETCHABLE = 1 << 0; + +// We save 12 bits in the "flags" section of our metadata for actual flags, the +// rest are to keep track of a rolling count of which loads a resource has been +// used on to determine if we can prefetch that resource or not; +static const uint8_t kRollingLoadOffset = 12; +static const int32_t kMaxPrefetchRollingLoadCount = 20; +static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1); + +// ID Extensions for cache entries +#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin" + +// Get the full origin (scheme, host, port) out of a URI (maybe should be part +// of nsIURI instead?) +static nsresult +ExtractOrigin(nsIURI *uri, nsIURI **originUri, nsIIOService *ioService) +{ + nsAutoCString s; + s.Truncate(); + nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewURI(originUri, s, nullptr, nullptr, ioService); +} + +// All URIs we get passed *must* be http or https if they're not null. This +// helps ensure that. +static bool +IsNullOrHttp(nsIURI *uri) +{ + if (!uri) { + return true; + } + + bool isHTTP = false; + uri->SchemeIs("http", &isHTTP); + if (!isHTTP) { + uri->SchemeIs("https", &isHTTP); + } + + return isHTTP; +} + +// Listener for the speculative DNS requests we'll fire off, which just ignores +// the result (since we're just trying to warm the cache). This also exists to +// reduce round-trips to the main thread, by being something threadsafe the +// Predictor can use. + +NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener); + +NS_IMETHODIMP +Predictor::DNSListener::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + return NS_OK; +} + +// Class to proxy important information from the initial predictor call through +// the cache API and back into the internals of the predictor. We can't use the +// predictor itself, as it may have multiple actions in-flight, and each action +// has different parameters. +NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback); + +Predictor::Action::Action(bool fullUri, bool predict, + Predictor::Reason reason, + nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier, + Predictor *predictor) + :mFullUri(fullUri) + ,mPredict(predict) + ,mTargetURI(targetURI) + ,mSourceURI(sourceURI) + ,mVerifier(verifier) + ,mStackCount(0) + ,mPredictor(predictor) +{ + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +Predictor::Action::Action(bool fullUri, bool predict, + Predictor::Reason reason, + nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier, + Predictor *predictor, uint8_t stackCount) + :mFullUri(fullUri) + ,mPredict(predict) + ,mTargetURI(targetURI) + ,mSourceURI(sourceURI) + ,mVerifier(verifier) + ,mStackCount(stackCount) + ,mPredictor(predictor) +{ + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +Predictor::Action::~Action() +{ } + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryCheck(nsICacheEntry *entry, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew, + nsIApplicationCache *appCache, + nsresult result) +{ + MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!"); + + nsAutoCString targetURI, sourceURI; + mTargetURI->GetAsciiSpec(targetURI); + if (mSourceURI) { + mSourceURI->GetAsciiSpec(sourceURI); + } + PREDICTOR_LOG(("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d " + "mPredictReason=%d mLearnReason=%d mTargetURI=%s " + "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08x", + this, entry, mFullUri, mPredict, mPredictReason, mLearnReason, + targetURI.get(), sourceURI.get(), mStackCount, + isNew, result)); + if (NS_FAILED(result)) { + PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08X). " + "Aborting.", this, result)); + return NS_OK; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, + mStartTime); + if (mPredict) { + bool predicted = mPredictor->PredictInternal(mPredictReason, entry, isNew, + mFullUri, mTargetURI, + mVerifier, mStackCount); + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_WORK_TIME, mStartTime); + if (predicted) { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime); + } else { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime); + } + } else { + mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI, + mSourceURI); + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_LEARN_WORK_TIME, mStartTime); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor, + nsINetworkPredictor, + nsIObserver, + nsISpeculativeConnectionOverrider, + nsIInterfaceRequestor, + nsICacheEntryMetaDataVisitor, + nsINetworkPredictorVerifier) + +Predictor::Predictor() + :mInitialized(false) + ,mEnabled(true) + ,mEnableHoverOnSSL(false) + ,mEnablePrefetch(true) + ,mPageDegradationDay(PREDICTOR_PAGE_DELTA_DAY_DEFAULT) + ,mPageDegradationWeek(PREDICTOR_PAGE_DELTA_WEEK_DEFAULT) + ,mPageDegradationMonth(PREDICTOR_PAGE_DELTA_MONTH_DEFAULT) + ,mPageDegradationYear(PREDICTOR_PAGE_DELTA_YEAR_DEFAULT) + ,mPageDegradationMax(PREDICTOR_PAGE_DELTA_MAX_DEFAULT) + ,mSubresourceDegradationDay(PREDICTOR_SUB_DELTA_DAY_DEFAULT) + ,mSubresourceDegradationWeek(PREDICTOR_SUB_DELTA_WEEK_DEFAULT) + ,mSubresourceDegradationMonth(PREDICTOR_SUB_DELTA_MONTH_DEFAULT) + ,mSubresourceDegradationYear(PREDICTOR_SUB_DELTA_YEAR_DEFAULT) + ,mSubresourceDegradationMax(PREDICTOR_SUB_DELTA_MAX_DEFAULT) + ,mPrefetchRollingLoadCount(PREFETCH_ROLLING_LOAD_DEFAULT) + ,mPrefetchMinConfidence(PREFETCH_MIN_DEFAULT) + ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT) + ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT) + ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT) + ,mPrefetchForceValidFor(PREFETCH_FORCE_VALID_DEFAULT) + ,mMaxResourcesPerEntry(PREDICTOR_MAX_RESOURCES_DEFAULT) + ,mStartupCount(1) + ,mMaxURILength(PREDICTOR_MAX_URI_LENGTH_DEFAULT) + ,mDoingTests(false) +{ + MOZ_ASSERT(!sSelf, "multiple Predictor instances!"); + sSelf = this; +} + +Predictor::~Predictor() +{ + if (mInitialized) + Shutdown(); + + sSelf = nullptr; +} + +// Predictor::nsIObserver + +nsresult +Predictor::InstallObserver() +{ + MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); + + nsresult rv = NS_OK; + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + Preferences::AddBoolVarCache(&mEnabled, PREDICTOR_ENABLED_PREF, true); + Preferences::AddBoolVarCache(&mEnableHoverOnSSL, + PREDICTOR_SSL_HOVER_PREF, false); + Preferences::AddBoolVarCache(&mEnablePrefetch, PREDICTOR_PREFETCH_PREF, true); + Preferences::AddIntVarCache(&mPageDegradationDay, + PREDICTOR_PAGE_DELTA_DAY_PREF, + PREDICTOR_PAGE_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationWeek, + PREDICTOR_PAGE_DELTA_WEEK_PREF, + PREDICTOR_PAGE_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMonth, + PREDICTOR_PAGE_DELTA_MONTH_PREF, + PREDICTOR_PAGE_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationYear, + PREDICTOR_PAGE_DELTA_YEAR_PREF, + PREDICTOR_PAGE_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMax, + PREDICTOR_PAGE_DELTA_MAX_PREF, + PREDICTOR_PAGE_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mSubresourceDegradationDay, + PREDICTOR_SUB_DELTA_DAY_PREF, + PREDICTOR_SUB_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationWeek, + PREDICTOR_SUB_DELTA_WEEK_PREF, + PREDICTOR_SUB_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMonth, + PREDICTOR_SUB_DELTA_MONTH_PREF, + PREDICTOR_SUB_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationYear, + PREDICTOR_SUB_DELTA_YEAR_PREF, + PREDICTOR_SUB_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMax, + PREDICTOR_SUB_DELTA_MAX_PREF, + PREDICTOR_SUB_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mPrefetchRollingLoadCount, + PREDICTOR_PREFETCH_ROLLING_LOAD_PREF, + PREFETCH_ROLLING_LOAD_DEFAULT); + Preferences::AddIntVarCache(&mPrefetchMinConfidence, + PREDICTOR_PREFETCH_MIN_PREF, + PREFETCH_MIN_DEFAULT); + Preferences::AddIntVarCache(&mPreconnectMinConfidence, + PREDICTOR_PRECONNECT_MIN_PREF, + PRECONNECT_MIN_DEFAULT); + Preferences::AddIntVarCache(&mPreresolveMinConfidence, + PREDICTOR_PRERESOLVE_MIN_PREF, + PRERESOLVE_MIN_DEFAULT); + Preferences::AddIntVarCache(&mRedirectLikelyConfidence, + PREDICTOR_REDIRECT_LIKELY_PREF, + REDIRECT_LIKELY_DEFAULT); + + Preferences::AddIntVarCache(&mPrefetchForceValidFor, + PREDICTOR_PREFETCH_FORCE_VALID_PREF, + PREFETCH_FORCE_VALID_DEFAULT); + + Preferences::AddIntVarCache(&mMaxResourcesPerEntry, + PREDICTOR_MAX_RESOURCES_PREF, + PREDICTOR_MAX_RESOURCES_DEFAULT); + + Preferences::AddBoolVarCache(&mCleanedUp, PREDICTOR_CLEANED_UP_PREF, false); + + Preferences::AddUintVarCache(&mMaxURILength, PREDICTOR_MAX_URI_LENGTH_PREF, + PREDICTOR_MAX_URI_LENGTH_DEFAULT); + + Preferences::AddBoolVarCache(&mDoingTests, PREDICTOR_DOING_TESTS_PREF, false); + + if (!mCleanedUp) { + mCleanupTimer = do_CreateInstance("@mozilla.org/timer;1"); + mCleanupTimer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT); + } + + return rv; +} + +void +Predictor::RemoveObserver() +{ + MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); + + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + if (mCleanupTimer) { + mCleanupTimer->Cancel(); + mCleanupTimer = nullptr; + } +} + +NS_IMETHODIMP +Predictor::Observe(nsISupports *subject, const char *topic, + const char16_t *data_unicode) +{ + nsresult rv = NS_OK; + MOZ_ASSERT(NS_IsMainThread(), + "Predictor observing something off main thread!"); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + } else if (!strcmp("timer-callback", topic)) { + MaybeCleanupOldDBFiles(); + mCleanupTimer = nullptr; + } + + return rv; +} + +// Predictor::nsISpeculativeConnectionOverrider + +NS_IMETHODIMP +Predictor::GetIgnoreIdle(bool *ignoreIdle) +{ + *ignoreIdle = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetParallelSpeculativeConnectLimit( + uint32_t *parallelSpeculativeConnectLimit) +{ + *parallelSpeculativeConnectLimit = 6; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetIsFromPredictor(bool *isFromPredictor) +{ + *isFromPredictor = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetAllow1918(bool *allow1918) +{ + *allow1918 = false; + return NS_OK; +} + +// Predictor::nsIInterfaceRequestor + +NS_IMETHODIMP +Predictor::GetInterface(const nsIID &iid, void **result) +{ + return QueryInterface(iid, result); +} + +// Predictor::nsICacheEntryMetaDataVisitor + +#define SEEN_META_DATA "predictor::seen" +#define RESOURCE_META_DATA "predictor::resource-count" +#define META_DATA_PREFIX "predictor::" + +static bool +IsURIMetadataElement(const char *key) +{ + return StringBeginsWith(nsDependentCString(key), + NS_LITERAL_CSTRING(META_DATA_PREFIX)) && + !NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) && + !NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key); +} + +nsresult +Predictor::OnMetaDataElement(const char *asciiKey, const char *asciiValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToOperateOn.AppendElement(key); + mValuesToOperateOn.AppendElement(value); + + return NS_OK; +} + +// Predictor::nsINetworkPredictor + +nsresult +Predictor::Init() +{ + MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild()); + + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Init called off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + + rv = InstallObserver(); + NS_ENSURE_SUCCESS(rv, rv); + + mLastStartupTime = mStartupTime = NOW_IN_SECONDS(); + + if (!mDNSListener) { + mDNSListener = new DNSListener(); + } + + nsCOMPtr cacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr lci = + new LoadContextInfo(false, NeckoOriginAttributes()); + + rv = cacheStorageService->DiskCacheStorage(lci, false, + getter_AddRefs(mCacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + mIOService = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(getter_AddRefs(mStartupURI), + "predictor://startup", nullptr, mIOService); + NS_ENSURE_SUCCESS(rv, rv); + + mSpeculativeService = do_QueryInterface(mIOService, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return rv; +} + +namespace { +class PredictorThreadShutdownRunner : public Runnable +{ +public: + PredictorThreadShutdownRunner(nsIThread *ioThread, bool success) + :mIOThread(ioThread) + ,mSuccess(success) + { } + ~PredictorThreadShutdownRunner() { } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!"); + if (mSuccess) { + // This means the cleanup happened. Mark so we don't try in the + // future. + Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true); + } + return mIOThread->AsyncShutdown(); + } + +private: + nsCOMPtr mIOThread; + bool mSuccess; +}; + +class PredictorOldCleanupRunner : public Runnable +{ +public: + PredictorOldCleanupRunner(nsIThread *ioThread, nsIFile *dbFile) + :mIOThread(ioThread) + ,mDBFile(dbFile) + { } + + ~PredictorOldCleanupRunner() { } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!"); + nsresult rv = CheckForAndDeleteOldDBFiles(); + RefPtr runner = + new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv)); + NS_DispatchToMainThread(runner); + return NS_OK; + } + +private: + nsresult CheckForAndDeleteOldDBFiles() + { + nsCOMPtr oldDBFile; + nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite")); + NS_ENSURE_SUCCESS(rv, rv); + + bool fileExists = false; + rv = oldDBFile->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileExists) { + rv = oldDBFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + + fileExists = false; + rv = mDBFile->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileExists) { + rv = mDBFile->Remove(false); + } + + return rv; + } + + nsCOMPtr mIOThread; + nsCOMPtr mDBFile; +}; + +} // namespace + +void +Predictor::MaybeCleanupOldDBFiles() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mEnabled || mCleanedUp) { + return; + } + + mCleanedUp = true; + + // This is used for cleaning up junk left over from the old backend + // built on top of sqlite, if necessary. + nsCOMPtr dbFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(dbFile)); + RETURN_IF_FAILED(rv); + rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite")); + RETURN_IF_FAILED(rv); + + nsCOMPtr ioThread; + rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread)); + RETURN_IF_FAILED(rv); + + RefPtr runner = + new PredictorOldCleanupRunner(ioThread, dbFile); + ioThread->Dispatch(runner, NS_DISPATCH_NORMAL); +} + +void +Predictor::Shutdown() +{ + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!"); + return; + } + + RemoveObserver(); + + mInitialized = false; +} + +nsresult +Predictor::Create(nsISupports *aOuter, const nsIID& aIID, + void **aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr svc = new Predictor(); + if (IsNeckoChild()) { + // Child threads only need to be call into the public interface methods + // so we don't bother with initialization + return svc->QueryInterface(aIID, aResult); + } + + rv = svc->Init(); + if (NS_FAILED(rv)) { + PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop")); + } + + // We treat init failure the same as the service being disabled, since this + // is all an optimization anyway. No need to freak people out. That's why we + // gladly continue on QI'ing here. + rv = svc->QueryInterface(aIID, aResult); + + return rv; +} + +// Called from the main thread to initiate predictive actions +NS_IMETHODIMP +Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI, + PredictorPredictReason reason, nsILoadContext *loadContext, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Predict")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + + ipc::OptionalURIParams serTargetURI, serSourceURI; + SerializeURI(targetURI, serTargetURI); + SerializeURI(sourceURI, serSourceURI); + + IPC::SerializedLoadContext serLoadContext; + serLoadContext.Init(loadContext); + + // If two different threads are predicting concurently, this will be + // overwritten. Thankfully, we only use this in tests, which will + // overwrite mVerifier perhaps multiple times for each individual test; + // however, within each test, the multiple predict calls should have the + // same verifier. + if (verifier) { + PREDICTOR_LOG((" was given a verifier")); + mChildVerifier = verifier; + } + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredPredict(serTargetURI, serSourceURI, + reason, serLoadContext, verifier); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + // Nothing we can do for non-HTTP[S] schemes + PREDICTOR_LOG((" got non-http[s] URI")); + return NS_OK; + } + + // Ensure we've been given the appropriate arguments for the kind of + // prediction we're being asked to do + nsCOMPtr 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, 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())); + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | + nsICacheStorage::CHECK_MULTITHREADED; + mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction); + + // Now we do the origin-only (and therefore predictor-only) entry + nsCOMPtr targetOrigin; + nsresult rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin), mIOService); + 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; + mCacheDiskStorage->AsyncOpenURI(originKey, + NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION), + openFlags, originAction); + + PREDICTOR_LOG((" predict returning")); + return NS_OK; +} + +bool +Predictor::PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry, + bool isNew, bool fullUri, nsIURI *targetURI, + nsINetworkPredictorVerifier *verifier, + uint8_t stackCount) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictInternal")); + bool rv = false; + + if (reason == nsINetworkPredictor::PREDICT_LOAD) { + MaybeLearnForStartup(targetURI, fullUri); + } + + if (isNew) { + // nothing else we can do here + PREDICTOR_LOG((" new entry")); + return rv; + } + + switch (reason) { + case nsINetworkPredictor::PREDICT_LOAD: + rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier); + break; + case nsINetworkPredictor::PREDICT_STARTUP: + rv = PredictForStartup(entry, fullUri, verifier); + break; + default: + PREDICTOR_LOG((" invalid reason")); + MOZ_ASSERT(false, "Got unexpected value for prediction reason"); + } + + return rv; +} + +void +Predictor::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForLink")); + if (!mSpeculativeService) { + PREDICTOR_LOG((" missing speculative service")); + return; + } + + if (!mEnableHoverOnSSL) { + bool isSSL = false; + sourceURI->SchemeIs("https", &isSSL); + if (isSSL) { + // We don't want to predict from an HTTPS page, to avoid info leakage + PREDICTOR_LOG((" Not predicting for link hover - on an SSL page")); + return; + } + } + + mSpeculativeService->SpeculativeConnect2(targetURI, nullptr, nullptr); + if (verifier) { + PREDICTOR_LOG((" sending verification")); + verifier->OnPredictPreconnect(targetURI); + } +} + +// This is the driver for prediction based on a new pageload. +static const uint8_t MAX_PAGELOAD_DEPTH = 10; +bool +Predictor::PredictForPageload(nsICacheEntry *entry, nsIURI *targetURI, + uint8_t stackCount, bool fullUri, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForPageload")); + + if (stackCount > MAX_PAGELOAD_DEPTH) { + PREDICTOR_LOG((" exceeded recursion depth!")); + return false; + } + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + NS_ENSURE_SUCCESS(rv, false); + + int32_t globalDegradation = CalculateGlobalDegradation(lastLoad); + PREDICTOR_LOG((" globalDegradation = %d", globalDegradation)); + + int32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr 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); + PREDICTOR_LOG((" Predict redirect uri=%s action=%p", redirectUriString.get(), + redirectAction.get())); + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | + nsICacheStorage::CHECK_MULTITHREADED; + mCacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags, + redirectAction); + return RunPredictions(nullptr, verifier); + } + + CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, fullUri); + + return RunPredictions(targetURI, verifier); +} + +// This is the driver for predicting at browser startup time based on pages that +// have previously been loaded close to startup. +bool +Predictor::PredictForStartup(nsICacheEntry *entry, bool fullUri, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForStartup")); + int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime); + CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount, + globalDegradation, fullUri); + return RunPredictions(nullptr, verifier); +} + +// This calculates how much to degrade our confidence in our data based on +// the last time this top-level resource was loaded. This "global degradation" +// applies to *all* subresources we have associated with the top-level +// resource. This will be in addition to any reduction in confidence we have +// associated with a particular subresource. +int32_t +Predictor::CalculateGlobalDegradation(uint32_t lastLoad) +{ + MOZ_ASSERT(NS_IsMainThread()); + + int32_t globalDegradation; + uint32_t delta = NOW_IN_SECONDS() - lastLoad; + if (delta < ONE_DAY) { + globalDegradation = mPageDegradationDay; + } else if (delta < ONE_WEEK) { + globalDegradation = mPageDegradationWeek; + } else if (delta < ONE_MONTH) { + globalDegradation = mPageDegradationMonth; + } else if (delta < ONE_YEAR) { + globalDegradation = mPageDegradationYear; + } else { + globalDegradation = mPageDegradationMax; + } + + Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION, + globalDegradation); + return globalDegradation; +} + +// This calculates our overall confidence that a particular subresource will be +// loaded as part of a top-level load. +// @param hitCount - the number of times we have loaded this subresource as part +// of this top-level load +// @param hitsPossible - the number of times we have performed this top-level +// load +// @param lastHit - the timestamp of the last time we loaded this subresource as +// part of this top-level load +// @param lastPossible - the timestamp of the last time we performed this +// top-level load +// @param globalDegradation - the degradation for this top-level load as +// determined by CalculateGlobalDegradation +int32_t +Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation) +{ + MOZ_ASSERT(NS_IsMainThread()); + + Telemetry::AutoCounter predictionsCalculated; + ++predictionsCalculated; + + if (!hitsPossible) { + return 0; + } + + int32_t baseConfidence = (hitCount * 100) / hitsPossible; + int32_t maxConfidence = 100; + int32_t confidenceDegradation = 0; + + if (lastHit < lastPossible) { + // We didn't load this subresource the last time this top-level load was + // performed, so let's not bother preconnecting (at the very least). + maxConfidence = mPreconnectMinConfidence - 1; + + // Now calculate how much we want to degrade our confidence based on how + // long it's been between the last time we did this top-level load and the + // last time this top-level load included this subresource. + PRTime delta = lastPossible - lastHit; + if (delta == 0) { + confidenceDegradation = 0; + } else if (delta < ONE_DAY) { + confidenceDegradation = mSubresourceDegradationDay; + } else if (delta < ONE_WEEK) { + confidenceDegradation = mSubresourceDegradationWeek; + } else if (delta < ONE_MONTH) { + confidenceDegradation = mSubresourceDegradationMonth; + } else if (delta < ONE_YEAR) { + confidenceDegradation = mSubresourceDegradationYear; + } else { + confidenceDegradation = mSubresourceDegradationMax; + maxConfidence = 0; + } + } + + // Calculate our confidence and clamp it to between 0 and maxConfidence + // (<= 100) + int32_t confidence = baseConfidence - confidenceDegradation - globalDegradation; + confidence = std::max(confidence, 0); + confidence = std::min(confidence, maxConfidence); + + Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence); + Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION, + confidenceDegradation); + Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence); + return confidence; +} + +static void +MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit, + const uint32_t flags, nsCString &newValue) +{ + newValue.Truncate(); + newValue.AppendInt(METADATA_VERSION); + newValue.Append(','); + newValue.AppendInt(hitCount); + newValue.Append(','); + newValue.AppendInt(lastHit); + newValue.Append(','); + newValue.AppendInt(flags); +} + +// On every page load, the rolling window gets shifted by one bit, leaving the +// lowest bit at 0, to indicate that the subresource in question has not been +// seen on the most recent page load. If, at some point later during the page load, +// the subresource is seen again, we will then set the lowest bit to 1. This is +// how we keep track of how many of the last n pageloads (for n <= 20) a particular +// subresource has been seen. +// The rolling window is kept in the upper 20 bits of the flags element of the +// metadata. This saves 12 bits for regular old flags. +void +Predictor::UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags, + const char *key, const uint32_t hitCount, + const uint32_t lastHit) +{ + // Extract just the rolling load count from the flags, shift it to clear the + // lowest bit, and put the new value with the existing flags. + uint32_t rollingLoadCount = flags & ~kFlagsMask; + rollingLoadCount <<= 1; + uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount; + + // Finally, update the metadata on the cache entry. + nsAutoCString newValue; + MakeMetadataEntry(hitCount, lastHit, newFlags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); +} + +void +Predictor::SanitizePrefs() +{ + if (mPrefetchRollingLoadCount < 0) { + mPrefetchRollingLoadCount = 0; + } else if (mPrefetchRollingLoadCount > kMaxPrefetchRollingLoadCount) { + mPrefetchRollingLoadCount = kMaxPrefetchRollingLoadCount; + } +} + +void +Predictor::CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri) +{ + MOZ_ASSERT(NS_IsMainThread()); + + SanitizePrefs(); + + // Since the visitor gets called under a cache lock, all we do there is get + // copies of the keys/values we care about, and then do the real work here + entry->VisitMetaData(this); + nsTArray keysToOperateOn, valuesToOperateOn; + keysToOperateOn.SwapElements(mKeysToOperateOn); + valuesToOperateOn.SwapElements(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char *key = keysToOperateOn[i].BeginReading(); + const char *value = valuesToOperateOn[i].BeginReading(); + + nsCOMPtr uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, getter_AddRefs(uri), hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + + int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit, + lastLoad, globalDegradation); + if (fullUri) { + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, value, confidence)); + if (!fullUri) { + // Not full URI - don't prefetch! No sense in it! + PREDICTOR_LOG((" forcing non-cacheability - not full URI")); + flags &= ~FLAG_PREFETCHABLE; + } else if (!referrer) { + // No referrer means we can't prefetch, so pretend it's non-cacheable, + // no matter what. + PREDICTOR_LOG((" forcing non-cacheability - no referrer")); + flags &= ~FLAG_PREFETCHABLE; + } else { + uint32_t expectedRollingLoadCount = (1 << mPrefetchRollingLoadCount) - 1; + expectedRollingLoadCount <<= kRollingLoadOffset; + if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) { + PREDICTOR_LOG((" forcing non-cacheability - missed a load")); + flags &= ~FLAG_PREFETCHABLE; + } + } + + PREDICTOR_LOG((" setting up prediction")); + SetupPrediction(confidence, flags, uri); + } +} + +// (Maybe) adds a predictive action to the prediction runner, based on our +// calculated confidence for the subresource in question. +void +Predictor::SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoCString uriStr; + uri->GetAsciiSpec(uriStr); + PREDICTOR_LOG(("SetupPrediction mEnablePrefetch=%d mPrefetchMinConfidence=%d " + "mPreconnectMinConfidence=%d mPreresolveMinConfidence=%d " + "flags=%d confidence=%d uri=%s", mEnablePrefetch, + mPrefetchMinConfidence, mPreconnectMinConfidence, + mPreresolveMinConfidence, flags, confidence, uriStr.get())); + if (mEnablePrefetch && (flags & FLAG_PREFETCHABLE) && + (mPrefetchRollingLoadCount || (confidence >= mPrefetchMinConfidence))) { + mPrefetches.AppendElement(uri); + } else if (confidence >= mPreconnectMinConfidence) { + mPreconnects.AppendElement(uri); + } else if (confidence >= mPreresolveMinConfidence) { + mPreresolves.AppendElement(uri); + } +} + +nsresult +Predictor::Prefetch(nsIURI *uri, nsIURI *referrer, + nsINetworkPredictorVerifier *verifier) +{ + nsAutoCString strUri, strReferrer; + uri->GetAsciiSpec(strUri); + referrer->GetAsciiSpec(strReferrer); + PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p", + strUri.get(), strReferrer.get(), verifier)); + nsCOMPtr channel; + nsresult rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, /* aLoadGroup */ + nullptr, /* aCallbacks */ + nsIRequest::LOAD_BACKGROUND); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" NS_NewChannel failed rv=0x%X", rv)); + return rv; + } + + nsCOMPtr httpChannel; + httpChannel = do_QueryInterface(channel); + if (!httpChannel) { + PREDICTOR_LOG((" Could not get HTTP Channel from new channel!")); + return NS_ERROR_UNEXPECTED; + } + + httpChannel->SetReferrer(referrer); + // XXX - set a header here to indicate this is a prefetch? + + nsCOMPtr listener = new PrefetchListener(verifier, uri, + this); + PREDICTOR_LOG((" calling AsyncOpen2 listener=%p channel=%p", listener.get(), + channel.get())); + rv = channel->AsyncOpen2(listener); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" AsyncOpen2 failed rv=0x%X", rv)); + } + + return rv; +} + +// Runs predictions that have been set up. +bool +Predictor::RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); + + PREDICTOR_LOG(("Predictor::RunPredictions")); + + bool predicted = false; + uint32_t len, i; + + nsTArray> prefetches, preconnects, preresolves; + prefetches.SwapElements(mPrefetches); + preconnects.SwapElements(mPreconnects); + preresolves.SwapElements(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, verifier))) { + ++totalPredictions; + ++totalPrefetches; + predicted = true; + } + } + + len = preconnects.Length(); + for (i = 0; i < len; ++i) { + PREDICTOR_LOG((" doing preconnect")); + nsCOMPtr uri = preconnects[i]; + ++totalPredictions; + ++totalPreconnects; + mSpeculativeService->SpeculativeConnect2(uri, nullptr, this); + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preconnect verification")); + verifier->OnPredictPreconnect(uri); + } + } + + len = preresolves.Length(); + nsCOMPtr mainThread = do_GetMainThread(); + 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->AsyncResolve(hostname, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + mDNSListener, nullptr, + getter_AddRefs(tmpCancelable)); + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preresolve verification")); + verifier->OnPredictDNS(uri); + } + } + + return predicted; +} + +// Find out if a top-level page is likely to redirect. +bool +Predictor::WouldRedirect(nsICacheEntry *entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI **redirectURI) +{ + // TODO - not doing redirects for first go around + MOZ_ASSERT(NS_IsMainThread()); + + return false; +} + +// Called from the main thread to update the database +NS_IMETHODIMP +Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadContext *loadContext) +{ + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Learn")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + + ipc::URIParams serTargetURI; + SerializeURI(targetURI, serTargetURI); + + ipc::OptionalURIParams serSourceURI; + SerializeURI(sourceURI, serSourceURI); + + IPC::SerializedLoadContext serLoadContext; + serLoadContext.Init(loadContext); + + PREDICTOR_LOG((" forwarding to parent")); + gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, reason, + serLoadContext); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + PREDICTOR_LOG((" got non-HTTP[S] URI")); + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr 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), mIOService); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = targetURI; + originKey = targetOrigin; + break; + case nsINetworkPredictor::LEARN_STARTUP: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" startup invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = mStartupURI; + originKey = mStartupURI; + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" redirect/subresource invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService); + NS_ENSURE_SUCCESS(rv, rv); + rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = sourceURI; + originKey = sourceOrigin; + break; + default: + PREDICTOR_LOG((" invalid reason")); + return NS_ERROR_INVALID_ARG; + } + + Telemetry::AutoCounter 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())); + // For learning full URI things, we *always* open readonly and secretly, as we + // rely on actual pageloads to update the entry's metadata for us. + uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // Learning for toplevel we want to open the full uri entry priority, since + // it's likely this entry will be used soon anyway, and we want this to be + // opened ASAP. + uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY; + } + mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags, + uriAction); + + // Now we open the origin-only (and therefore predictor-only) entry + RefPtr originAction = + new Predictor::Action(Predictor::Action::IS_ORIGIN, + Predictor::Action::DO_LEARN, argReason, targetOrigin, + sourceOrigin, nullptr, this); + nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr; + originKey->GetAsciiSpec(originKeyStr); + targetOrigin->GetAsciiSpec(targetOriginStr); + if (sourceOrigin) { + sourceOrigin->GetAsciiSpec(sourceOriginStr); + } + PREDICTOR_LOG((" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d " + "action=%p", originKeyStr.get(), targetOriginStr.get(), + sourceOriginStr.get(), reason, originAction.get())); + uint32_t originOpenFlags; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // This is the only case when we want to update the 'last used' metadata on + // the cache entry we're getting. This only applies to predictor-specific + // entries. + originOpenFlags = nsICacheStorage::OPEN_NORMALLY | + nsICacheStorage::CHECK_MULTITHREADED; + } else { + originOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + } + mCacheDiskStorage->AsyncOpenURI(originKey, + NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION), + originOpenFlags, originAction); + + PREDICTOR_LOG(("Predictor::Learn returning")); + return NS_OK; +} + +void +Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry, + bool isNew, bool fullUri, nsIURI *targetURI, + nsIURI *sourceURI) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnInternal")); + + nsCString junk; + if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL && + NS_FAILED(entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) { + // This is an origin-only entry that we haven't seen before. Let's mark it + // as seen. + PREDICTOR_LOG((" marking new origin entry as seen")); + nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1"); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to mark origin entry seen")); + return; + } + + // Need to ensure someone else can get to the entry if necessary + entry->MetaDataReady(); + return; + } + + switch (reason) { + case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL: + // This case only exists to be used during tests - code outside the + // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL. + // The predictor xpcshell test needs this branch, however, because we + // have no real page loads in xpcshell, and this is how we fake it up + // so that all the work that normally happens behind the scenes in a + // page load can be done for testing purposes. + if (fullUri && mDoingTests) { + PREDICTOR_LOG((" WARNING - updating rolling load count. " + "If you see this outside tests, you did it wrong")); + SanitizePrefs(); + + // Since the visitor gets called under a cache lock, all we do there is get + // copies of the keys/values we care about, and then do the real work here + entry->VisitMetaData(this); + nsTArray keysToOperateOn, valuesToOperateOn; + keysToOperateOn.SwapElements(mKeysToOperateOn); + valuesToOperateOn.SwapElements(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char *key = keysToOperateOn[i].BeginReading(); + const char *value = valuesToOperateOn[i].BeginReading(); + + nsCOMPtr uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(nullptr, value, nullptr, hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + } else { + PREDICTOR_LOG((" nothing to do for toplevel")); + } + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + if (fullUri) { + LearnForRedirect(entry, targetURI); + } + break; + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + LearnForSubresource(entry, targetURI); + break; + case nsINetworkPredictor::LEARN_STARTUP: + LearnForStartup(entry, targetURI); + break; + default: + PREDICTOR_LOG((" unexpected reason value")); + MOZ_ASSERT(false, "Got unexpected value for learn reason!"); + } +} + +NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor) + +NS_IMETHODIMP +Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(key)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + uint32_t hitCount, lastHit, flags; + bool ok = mPredictor->ParseMetaDataEntry(nullptr, value, nullptr, + hitCount, lastHit, flags); + + if (!ok) { + // Couldn't parse this one, just get rid of it + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + nsCString uri(key + (sizeof(META_DATA_PREFIX) - 1)); + uint32_t uriLength = uri.Length(); + if (uriLength > mPredictor->mMaxURILength) { + // Default to getting rid of URIs that are too long and were put in before + // we had our limit on URI length, in order to free up some space. + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + if (!mLRUKeyToDelete || lastHit < mLRUStamp) { + mLRUKeyToDelete = key; + mLRUStamp = lastHit; + } + + return NS_OK; +} + +void +Predictor::SpaceCleaner::Finalize(nsICacheEntry *entry) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mLRUKeyToDelete) { + entry->SetMetaDataElement(mLRUKeyToDelete, nullptr); + } + + for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) { + entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr); + } +} + +// Called when a subresource has been hit from a top-level load. Uses the two +// helper functions above to update the database appropriately. +void +Predictor::LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnForSubresource")); + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + RETURN_IF_FAILED(rv); + + int32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + RETURN_IF_FAILED(rv); + + nsCString key; + key.AssignLiteral(META_DATA_PREFIX); + nsCString uri; + targetURI->GetAsciiSpec(uri); + key.Append(uri); + if (uri.Length() > mMaxURILength) { + // We do this to conserve space/prevent OOMs + PREDICTOR_LOG((" uri too long!")); + entry->SetMetaDataElement(key.BeginReading(), nullptr); + return; + } + + nsCString value; + rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value)); + + uint32_t hitCount, lastHit, flags; + bool isNewResource = (NS_FAILED(rv) || + !ParseMetaDataEntry(nullptr, value.BeginReading(), + nullptr, hitCount, lastHit, flags)); + + int32_t resourceCount = 0; + if (isNewResource) { + // This is a new addition + PREDICTOR_LOG((" new resource")); + nsCString s; + rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s)); + if (NS_SUCCEEDED(rv)) { + resourceCount = atoi(s.BeginReading()); + } + if (resourceCount >= mMaxResourcesPerEntry) { + RefPtr cleaner = + new Predictor::SpaceCleaner(this); + entry->VisitMetaData(cleaner); + cleaner->Finalize(entry); + } else { + ++resourceCount; + } + nsAutoCString count; + count.AppendInt(resourceCount); + rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to update resource count")); + return; + } + hitCount = 1; + flags = 0; + } else { + PREDICTOR_LOG((" existing resource")); + hitCount = std::min(hitCount + 1, static_cast(loadCount)); + } + + // Update the rolling load count to mark this sub-resource as seen on the + // most-recent pageload so it can be eligible for prefetch (assuming all + // the other stars align). + flags |= (1 << kRollingLoadOffset); + + nsCString newValue; + MakeMetadataEntry(hitCount, lastLoad, flags, newValue); + rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading()); + PREDICTOR_LOG((" SetMetaDataElement -> 0x%08X", rv)); + if (NS_FAILED(rv) && isNewResource) { + // Roll back the increment to the resource count we made above. + PREDICTOR_LOG((" rolling back resource count update")); + --resourceCount; + if (resourceCount == 0) { + entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr); + } else { + nsAutoCString count; + count.AppendInt(resourceCount); + entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + } + } +} + +// This is called when a top-level loaded ended up redirecting to a different +// URI so we can keep track of that fact. +void +Predictor::LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing redirects for first go around + PREDICTOR_LOG(("Predictor::LearnForRedirect")); +} + +// This will add a page to our list of startup pages if it's being loaded +// before our startup window has expired. +void +Predictor::MaybeLearnForStartup(nsIURI *uri, bool fullUri) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing startup for first go around + PREDICTOR_LOG(("Predictor::MaybeLearnForStartup")); +} + +// Add information about a top-level load to our list of startup pages +void +Predictor::LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // These actually do the same set of work, just on different entries, so we + // can pass through to get the real work done here + PREDICTOR_LOG(("Predictor::LearnForStartup")); + LearnForSubresource(entry, targetURI); +} + +bool +Predictor::ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri, + uint32_t &hitCount, uint32_t &lastHit, + uint32_t &flags) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::ParseMetaDataEntry key=%s value=%s", + key ? key : "", value)); + + const char *comma = strchr(value, ','); + if (!comma) { + PREDICTOR_LOG((" could not find first comma")); + return false; + } + + uint32_t version = static_cast(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); + nsresult rv = NS_NewURI(uri, uriStart, nullptr, mIOService); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" NS_NewURI returned 0x%X", rv)); + return false; + } + PREDICTOR_LOG((" uri -> %s", uriStart)); + } + + return true; +} + +NS_IMETHODIMP +Predictor::Reset() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Reset")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredReset(); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + RefPtr reset = new Predictor::Resetter(this); + PREDICTOR_LOG((" created a resetter")); + mCacheDiskStorage->AsyncVisitStorage(reset, true); + PREDICTOR_LOG((" Cache async launched, returning now")); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor::Resetter, + nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor, + nsICacheStorageVisitor); + +Predictor::Resetter::Resetter(Predictor *predictor) + :mEntriesToVisit(0) + ,mPredictor(predictor) +{ } + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry *entry, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew, + nsIApplicationCache *appCache, + nsresult result) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_FAILED(result)) { + // This can happen when we've tried to open an entry that doesn't exist for + // some non-reset operation, and then get reset shortly thereafter (as + // happens in some of our tests). + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + return NS_OK; + } + + entry->VisitMetaData(this); + nsTArray keysToDelete; + keysToDelete.SwapElements(mKeysToDelete); + + for (size_t i = 0; i < keysToDelete.Length(); ++i) { + const char *key = keysToDelete[i].BeginReading(); + entry->SetMetaDataElement(key, nullptr); + } + + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnMetaDataElement(const char *asciiKey, + const char *asciiValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!StringBeginsWith(nsDependentCString(asciiKey), + NS_LITERAL_CSTRING(META_DATA_PREFIX))) { + // Not a metadata entry we care about, carry on + return NS_OK; + } + + nsCString key; + key.AssignASCII(asciiKey); + mKeysToDelete.AppendElement(key); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, uint64_t consumption, + uint64_t capacity, nsIFile *diskDirectory) +{ + MOZ_ASSERT(NS_IsMainThread()); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryInfo(nsIURI *uri, const nsACString &idEnhance, + int64_t dataSize, int32_t fetchCount, + uint32_t lastModifiedTime, uint32_t expirationTime, + bool aPinned) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // The predictor will only ever touch entries with no idEnhance ("") or an + // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that + // don't match that to avoid doing extra work. + if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) { + // This is an entry we own, so we can just doom it entirely + mPredictor->mCacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr); + } else if (idEnhance.IsEmpty()) { + // This is an entry we don't own, so we have to be a little more careful and + // just get rid of our own metadata entries. Append it to an array of things + // to operate on and then do the operations later so we don't end up calling + // Complete() multiple times/too soon. + ++mEntriesToVisit; + mURIsToVisit.AppendElement(uri); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryVisitCompleted() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray> urisToVisit; + urisToVisit.SwapElements(mURIsToVisit); + + MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length()); + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + + uint32_t entriesToVisit = urisToVisit.Length(); + for (uint32_t i = 0; i < entriesToVisit; ++i) { + nsCString u; + urisToVisit[i]->GetAsciiSpec(u); + mPredictor->mCacheDiskStorage->AsyncOpenURI( + urisToVisit[i], EmptyCString(), + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED, + this); + } + + return NS_OK; +} + +void +Predictor::Resetter::Complete() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!")); + return; + } + + obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr); +} + +// Helper functions to make using the predictor easier from native code + +static nsresult +EnsureGlobalPredictor(nsINetworkPredictor **aPredictor) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr predictor = + do_GetService("@mozilla.org/network/predictor;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + predictor.forget(aPredictor); + return NS_OK; +} + +nsresult +PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI, + PredictorPredictReason reason, nsILoadContext *loadContext, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->Predict(targetURI, sourceURI, reason, + loadContext, verifier); +} + +nsresult +PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadContext *loadContext) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadGroup *loadGroup) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr loadContext; + + if (loadGroup) { + nsCOMPtr callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + loadContext = do_GetInterface(callbacks); + } + } + + return predictor->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsIDocument *document) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr loadContext; + + if (document) { + loadContext = document->GetLoadContext(); + } + + return predictor->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel, + nsILoadContext *loadContext) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr 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->Learn(targetURI, sourceURI, + nsINetworkPredictor::LEARN_LOAD_REDIRECT, + loadContext); +} + +// nsINetworkPredictorVerifier + +/** + * Call through to the child's verifier (only during tests) + */ +NS_IMETHODIMP +Predictor::OnPredictPrefetch(nsIURI *aURI, uint32_t httpStatus) +{ + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child process + // will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPrefetch(aURI, httpStatus); + } + return NS_OK; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPrefetch(serURI, httpStatus)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictPreconnect(nsIURI *aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child process + // will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPreconnect(aURI); + } + return NS_OK; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPreconnect(serURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictDNS(nsIURI *aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child process + // will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictDNS(aURI); + } + return NS_OK; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictDNS(serURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +// Predictor::PrefetchListener +// nsISupports +NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, + nsIStreamListener, + nsIRequestObserver) + +// nsIRequestObserver +NS_IMETHODIMP +Predictor::PrefetchListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + mStartTime = TimeStamp::Now(); + return NS_OK; +} + +NS_IMETHODIMP +Predictor::PrefetchListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%X", this, aStatusCode)); + NS_ENSURE_ARG(aRequest); + if (NS_FAILED(aStatusCode)) { + return aStatusCode; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, mStartTime); + + nsCOMPtr 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(mPredictor->mPrefetchForceValidFor); + PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%X", + mPredictor->mPrefetchForceValidFor, rv)); + } else { + rv = cachingChannel->ForceCacheEntryValidFor(0); + PREDICTOR_LOG((" removing any forced validity rv=%X", rv)); + } + + nsAutoCString reqName; + rv = aRequest->GetName(reqName); + if (NS_FAILED(rv)) { + reqName.AssignLiteral(""); + } + + PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus)); + + if (mVerifier) { + mVerifier->OnPredictPrefetch(mURI, httpStatus); + } + + return rv; +} + +// nsIStreamListener +NS_IMETHODIMP +Predictor::PrefetchListener::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, + const uint32_t aCount) +{ + uint32_t result; + return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result); +} + +// Miscellaneous Predictor + +void +Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI, + uint32_t httpStatus, + nsHttpRequestHead &requestHead, + nsHttpResponseHead *responseHead, + nsILoadContextInfo *lci) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (lci && lci->IsPrivate()) { + PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring")); + return; + } + + if (!sourceURI || !targetURI) { + PREDICTOR_LOG(("Predictor::UpdateCacheability missing source or target uri")); + return; + } + + if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) { + PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri")); + return; + } + + RefPtr self = sSelf; + if (self) { + nsAutoCString method; + requestHead.Method(method); + self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, + method); + } +} + +void +Predictor::UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI, + uint32_t httpStatus, + const nsCString &method) +{ + PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus)); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return; + } + + if (!mEnablePrefetch) { + PREDICTOR_LOG((" prefetch not enabled")); + return; + } + + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + RefPtr action = + new Predictor::CacheabilityAction(targetURI, httpStatus, method, this); + nsAutoCString uri; + targetURI->GetAsciiSpec(uri); + PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get())); + mCacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action); +} + +NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, + nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor); + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry *entry, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry *entry, + bool isNew, + nsIApplicationCache *appCache, + nsresult result) +{ + MOZ_ASSERT(NS_IsMainThread()); + // This is being opened read-only, so isNew should always be false + MOZ_ASSERT(!isNew); + + PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this)); + if (NS_FAILED(result)) { + // Nothing to do + PREDICTOR_LOG((" nothing to do result=%X isNew=%d", result, isNew)); + return NS_OK; + } + + nsresult rv = entry->VisitMetaData(this); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" VisitMetaData returned %x", rv)); + return NS_OK; + } + + nsTArray keysToCheck, valuesToCheck; + keysToCheck.SwapElements(mKeysToCheck); + valuesToCheck.SwapElements(mValuesToCheck); + + MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length()); + for (size_t i = 0; i < keysToCheck.Length(); ++i) { + const char *key = keysToCheck[i].BeginReading(); + const char *value = valuesToCheck[i].BeginReading(); + nsCOMPtr uri; + uint32_t hitCount, lastHit, flags; + + if (!mPredictor->ParseMetaDataEntry(key, value, getter_AddRefs(uri), + hitCount, lastHit, flags)) { + PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value)); + continue; + } + + bool eq = false; + if (NS_SUCCEEDED(uri->Equals(mTargetURI, &eq)) && eq) { + if (mHttpStatus == 200 && mMethod.EqualsLiteral("GET")) { + PREDICTOR_LOG((" marking %s cacheable", key)); + flags |= FLAG_PREFETCHABLE; + } else { + PREDICTOR_LOG((" marking %s uncacheable", key)); + flags &= ~FLAG_PREFETCHABLE; + } + nsCString newValue; + MakeMetadataEntry(hitCount, lastHit, flags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnMetaDataElement(const char *asciiKey, + const char *asciiValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToCheck.AppendElement(key); + mValuesToCheck.AppendElement(value); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h new file mode 100644 index 000000000..69c597598 --- /dev/null +++ b/netwerk/base/Predictor.h @@ -0,0 +1,480 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Predictor_h +#define mozilla_net_Predictor_h + +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" + +#include "nsCOMPtr.h" +#include "nsICacheEntry.h" +#include "nsICacheEntryOpenCallback.h" +#include "nsICacheStorageVisitor.h" +#include "nsIDNSListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsISpeculativeConnect.h" +#include "nsIStreamListener.h" +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "mozilla/TimeStamp.h" + +class nsICacheStorage; +class nsIDNSService; +class nsIIOService; +class nsILoadContextInfo; +class nsITimer; + +namespace mozilla { +namespace net { + +class nsHttpRequestHead; +class nsHttpResponseHead; + +class Predictor : public nsINetworkPredictor + , public nsIObserver + , public nsISpeculativeConnectionOverrider + , public nsIInterfaceRequestor + , public nsICacheEntryMetaDataVisitor + , public nsINetworkPredictorVerifier +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKPREDICTOR + NS_DECL_NSIOBSERVER + NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSINETWORKPREDICTORVERIFIER + + Predictor(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(nsISupports *outer, const nsIID& iid, void **result); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + static void UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI, + uint32_t httpStatus, + nsHttpRequestHead &requestHead, + nsHttpResponseHead *reqponseHead, + nsILoadContextInfo *lci); + +private: + virtual ~Predictor(); + + // Stores callbacks for a child process predictor (for test purposes) + nsCOMPtr mChildVerifier; + + union Reason { + PredictorLearnReason mLearn; + PredictorPredictReason mPredict; + }; + + class DNSListener : public nsIDNSListener + { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + DNSListener() + { } + + private: + virtual ~DNSListener() + { } + }; + + class Action : public nsICacheEntryOpenCallback + { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + + Action(bool fullUri, bool predict, Reason reason, + nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier, Predictor *predictor); + Action(bool fullUri, bool predict, Reason reason, + nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier, Predictor *predictor, + uint8_t stackCount); + + static const bool IS_FULL_URI = true; + static const bool IS_ORIGIN = false; + + static const bool DO_PREDICT = true; + static const bool DO_LEARN = false; + + private: + virtual ~Action(); + + bool mFullUri : 1; + bool mPredict : 1; + union { + PredictorPredictReason mPredictReason; + PredictorLearnReason mLearnReason; + }; + nsCOMPtr 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, Predictor *predictor) + :mTargetURI(targetURI) + ,mHttpStatus(httpStatus) + ,mMethod(method) + ,mPredictor(predictor) + { } + + private: + virtual ~CacheabilityAction() { } + + nsCOMPtr mTargetURI; + uint32_t mHttpStatus; + nsCString mMethod; + 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() { } + + void Complete(); + + uint32_t mEntriesToVisit; + nsTArray mKeysToDelete; + RefPtr mPredictor; + nsTArray> mURIsToVisit; + }; + + class SpaceCleaner : public nsICacheEntryMetaDataVisitor + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYMETADATAVISITOR + + explicit SpaceCleaner(Predictor *predictor) + :mLRUStamp(0) + ,mLRUKeyToDelete(nullptr) + ,mPredictor(predictor) + { } + + void Finalize(nsICacheEntry *entry); + + private: + virtual ~SpaceCleaner() { } + uint32_t mLRUStamp; + const char *mLRUKeyToDelete; + nsTArray 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() { } + + 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 + // * verifier - used for testing to verify the expected predictions happen + void PredictForLink(nsIURI *targetURI, + nsIURI *sourceURI, + nsINetworkPredictorVerifier *verifier); + + // Used when predicting because a page is being loaded (which may include + // being the target of a redirect). All arguments are the same as for + // PredictInternal. Returns true if any predictions were queued up. + bool PredictForPageload(nsICacheEntry *entry, + nsIURI *targetURI, + uint8_t stackCount, + bool fullUri, + nsINetworkPredictorVerifier *verifier); + + // Used when predicting pages that will be used near browser startup. All + // arguments are the same as for PredictInternal. Returns true if any + // predictions were queued up. + bool PredictForStartup(nsICacheEntry *entry, + bool fullUri, + nsINetworkPredictorVerifier *verifier); + + // Utilities related to prediction + + // Used to update our rolling load count (how many of the last n loads was a + // partular resource loaded on?) + // * entry - cache entry of page we're loading + // * flags - value that contains our rolling count as the top 20 bits (but + // we may use fewer than those 20 bits for calculations) + // * key - metadata key that we will update on entry + // * hitCount - part of the metadata we need to preserve + // * lastHit - part of the metadata we need to preserve + void UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags, + const char *key, const uint32_t hitCount, + const uint32_t lastHit); + + // Used to calculate how much to degrade our confidence for all resources + // on a particular page, because of how long ago the most recent load of that + // page was. Returns a value between 0 (very recent most recent load) and 100 + // (very distant most recent load) + // * lastLoad - time stamp of most recent load of a page + int32_t CalculateGlobalDegradation(uint32_t lastLoad); + + // Used to calculate how confident we are that a particular resource will be + // used. Returns a value between 0 (no confidence) and 100 (very confident) + // * hitCount - number of times this resource has been seen when loading + // this page + // * hitsPossible - number of times this page has been loaded + // * lastHit - timestamp of the last time this resource was seen when + // loading this page + // * lastPossible - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation); + + // Used to calculate all confidence values for all resources associated with a + // page. + // * entry - the cache entry with all necessary information about this page + // * referrer - the URI that we are loading (may be null) + // * lastLoad - timestamp of the last time this page was loaded + // * loadCount - number of times this page has been loaded + // * gloablDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * fullUri - whether we're predicting for a full URI or origin-only + void CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri); + + // Used to prepare any necessary prediction for a resource on a page + // * confidence - value calculated by CalculateConfidence for this resource + // * flags - the flags taken from the resource + // * uri - the URI of the resource + void SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri); + + // Used to kick off a prefetch from RunPredictions if necessary + // * uri - the URI to prefetch + // * referrer - the URI of the referring page + // * verifier - used for testing to ensure the expected prefetch happens + nsresult Prefetch(nsIURI *uri, nsIURI *referrer, nsINetworkPredictorVerifier *verifier); + + // Used to actually perform any predictions set up via SetupPrediction. + // Returns true if any predictions were performed. + // * referrer - the URI we are predicting from + // * verifier - used for testing to ensure the expected predictions happen + bool RunPredictions(nsIURI *referrer, nsINetworkPredictorVerifier *verifier); + + // Used to guess whether a page will redirect to another page or not. Returns + // true if a redirection is likely. + // * entry - cache entry with all necessary information about this page + // * loadCount - number of times this page has been loaded + // * lastLoad - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * redirectURI - if this returns true, the URI that is likely to be + // redirected to, otherwise null + bool WouldRedirect(nsICacheEntry *entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI **redirectURI); + + // The guts of learning information + + // This is the top-level driver for doing any updating of our information in + // the cache + // * reason - why this learn is happening (pageload, startup, redirect) + // * entry - the cache entry with the information we need + // * isNew - whether or not the cache entry is brand new and empty + // * fullUri - whether we are doing predictions based on a full page URI, or + // just the origin of the page + // * targetURI - the URI that we are adding to our data - most often a + // resource loaded by a page the user navigated to + // * sourceURI - the URI that caused targetURI to be loaded, usually the + // page the user navigated to + void LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry, + bool isNew, bool fullUri, nsIURI *targetURI, + nsIURI *sourceURI); + + // Used when learning about a resource loaded by a page + // * entry - the cache entry with information that needs updating + // * targetURI - the URI of the resource that was loaded by the page + void LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI); + + // Used when learning about a redirect from one page to another + // * entry - the cache entry of the page that was redirected from + // * targetURI - the URI of the redirect target + void LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI); + + // Used to learn about pages loaded close to browser startup. This results in + // LearnForStartup being called if we are, in fact, near browser startup + // * uri - the URI of a page that has been loaded (may not have been near + // browser startup) + // * fullUri - true if this is a full page uri, false if it's an origin + void MaybeLearnForStartup(nsIURI *uri, bool fullUri); + + // Used in conjunction with MaybeLearnForStartup to learn about pages loaded + // close to browser startup + // * entry - the cache entry that stores the startup page list + // * targetURI - the URI of a page that was loaded near browser startup + void LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI); + + // Used to parse the data we store in cache metadata + // * key - the cache metadata key + // * value - the cache metadata value + // * uri - (out) the URI this metadata entry was about + // * hitCount - (out) the number of times this URI has been seen + // * lastHit - (out) timestamp of the last time this URI was seen + // * flags - (out) flags for this metadata entry + bool ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri, + uint32_t &hitCount, uint32_t &lastHit, + uint32_t &flags); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + void UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI, + uint32_t httpStatus, const nsCString &method); + + // Make sure our prefs are in their expected range of values + void SanitizePrefs(); + + // Our state + bool mInitialized; + + bool mEnabled; + bool mEnableHoverOnSSL; + bool mEnablePrefetch; + + int32_t mPageDegradationDay; + int32_t mPageDegradationWeek; + int32_t mPageDegradationMonth; + int32_t mPageDegradationYear; + int32_t mPageDegradationMax; + + int32_t mSubresourceDegradationDay; + int32_t mSubresourceDegradationWeek; + int32_t mSubresourceDegradationMonth; + int32_t mSubresourceDegradationYear; + int32_t mSubresourceDegradationMax; + + int32_t mPrefetchRollingLoadCount; + int32_t mPrefetchMinConfidence; + int32_t mPreconnectMinConfidence; + int32_t mPreresolveMinConfidence; + int32_t mRedirectLikelyConfidence; + + int32_t mPrefetchForceValidFor; + + int32_t mMaxResourcesPerEntry; + + bool mCleanedUp; + nsCOMPtr mCleanupTimer; + + nsTArray mKeysToOperateOn; + nsTArray mValuesToOperateOn; + + nsCOMPtr mCacheDiskStorage; + + nsCOMPtr mIOService; + nsCOMPtr mSpeculativeService; + + nsCOMPtr mStartupURI; + uint32_t mStartupTime; + uint32_t mLastStartupTime; + int32_t mStartupCount; + + uint32_t mMaxURILength; + + nsCOMPtr mDnsService; + + RefPtr mDNSListener; + + nsTArray> mPrefetches; + nsTArray> mPreconnects; + nsTArray> mPreresolves; + + bool mDoingTests; + + static Predictor *sSelf; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Predictor_h diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h new file mode 100644 index 000000000..10c664502 --- /dev/null +++ b/netwerk/base/PrivateBrowsingChannel.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_PrivateBrowsingChannel_h__ +#define mozilla_net_PrivateBrowsingChannel_h__ + +#include "nsIPrivateBrowsingChannel.h" +#include "nsCOMPtr.h" +#include "nsILoadGroup.h" +#include "nsILoadContext.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInterfaceRequestor.h" +#include "nsNetUtil.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace net { + +template +class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel +{ +public: + PrivateBrowsingChannel() : + mPrivateBrowsingOverriden(false), + mPrivateBrowsing(false) + { + } + + NS_IMETHOD SetPrivate(bool aPrivate) + { + // Make sure that we don't have a load context + // This is a fatal error in debug builds, and a runtime error in release + // builds. + nsCOMPtr 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) + { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsing; + return NS_OK; + } + + NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool *aResult) + { + NS_ENSURE_ARG_POINTER(aValue); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsingOverriden; + if (mPrivateBrowsingOverriden) { + *aValue = mPrivateBrowsing; + } + return NS_OK; + } + + // Must be called every time the channel's callbacks or loadGroup is updated + void UpdatePrivateBrowsing() + { + // once marked as private we never go un-private + if (mPrivateBrowsing) { + return; + } + + auto channel = static_cast(this); + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(channel, loadContext); + if (loadContext) { + mPrivateBrowsing = loadContext->UsePrivateBrowsing(); + return; + } + + nsCOMPtr loadInfo; + Unused << channel->GetLoadInfo(getter_AddRefs(loadInfo)); + if (loadInfo) { + NeckoOriginAttributes attrs = loadInfo->GetOriginAttributes(); + mPrivateBrowsing = attrs.mPrivateBrowsingId > 0; + } + } + + bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const + { + // Make sure that the private bit override flag is not set. + // This is a fatal error in debug builds, and a runtime error in release + // builds. + if (!aCallbacks) { + return true; + } + nsCOMPtr 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/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp new file mode 100644 index 000000000..4d7a6c1fd --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -0,0 +1,1015 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProxyAutoConfig.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsThreadUtils.h" +#include "nsIConsoleService.h" +#include "nsIURLParser.h" +#include "nsJSUtils.h" +#include "jsfriendapi.h" +#include "prnetdb.h" +#include "nsITimer.h" +#include "mozilla/net/DNS.h" +#include "nsServiceManagerUtils.h" +#include "nsNetCID.h" + +namespace mozilla { +namespace net { + +// These are some global helper symbols the PAC format requires that we provide that +// are initialized as part of the global javascript context used for PAC evaluations. +// Additionally dnsResolve(host) and myIpAddress() are supplied in the same context +// but are implemented as c++ helpers. alert(msg) is similarly defined. + +static const char *sPacUtils = + "function dnsDomainIs(host, domain) {\n" + " return (host.length >= domain.length &&\n" + " host.substring(host.length - domain.length) == domain);\n" + "}\n" + "" + "function dnsDomainLevels(host) {\n" + " return host.split('.').length - 1;\n" + "}\n" + "" + "function convert_addr(ipchars) {\n" + " var bytes = ipchars.split('.');\n" + " var result = ((bytes[0] & 0xff) << 24) |\n" + " ((bytes[1] & 0xff) << 16) |\n" + " ((bytes[2] & 0xff) << 8) |\n" + " (bytes[3] & 0xff);\n" + " return result;\n" + "}\n" + "" + "function isInNet(ipaddr, pattern, maskstr) {\n" + " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" + " if (test == null) {\n" + " ipaddr = dnsResolve(ipaddr);\n" + " if (ipaddr == null)\n" + " return false;\n" + " } else if (test[1] > 255 || test[2] > 255 || \n" + " test[3] > 255 || test[4] > 255) {\n" + " return false; // not an IP address\n" + " }\n" + " var host = convert_addr(ipaddr);\n" + " var pat = convert_addr(pattern);\n" + " var mask = convert_addr(maskstr);\n" + " return ((host & mask) == (pat & mask));\n" + " \n" + "}\n" + "" + "function isPlainHostName(host) {\n" + " return (host.search('\\\\.') == -1);\n" + "}\n" + "" + "function isResolvable(host) {\n" + " var ip = dnsResolve(host);\n" + " return (ip != null);\n" + "}\n" + "" + "function localHostOrDomainIs(host, hostdom) {\n" + " return (host == hostdom) ||\n" + " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" + "}\n" + "" + "function shExpMatch(url, pattern) {\n" + " pattern = pattern.replace(/\\./g, '\\\\.');\n" + " pattern = pattern.replace(/\\*/g, '.*');\n" + " pattern = pattern.replace(/\\?/g, '.');\n" + " var newRe = new RegExp('^'+pattern+'$');\n" + " return newRe.test(url);\n" + "}\n" + "" + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" + "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" + "" + "function weekdayRange() {\n" + " function getDay(weekday) {\n" + " if (weekday in wdays) {\n" + " return wdays[weekday];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " var wday;\n" + " if (argc < 1)\n" + " return false;\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " argc--;\n" + " wday = date.getUTCDay();\n" + " } else {\n" + " wday = date.getDay();\n" + " }\n" + " var wd1 = getDay(arguments[0]);\n" + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" + " return (wd1 == -1 || wd2 == -1) ? false\n" + " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n" + " : (wd2 >= wday || wday >= wd1);\n" + "}\n" + "" + "function dateRange() {\n" + " function getMonth(name) {\n" + " if (name in months) {\n" + " return months[name];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " var isGMT = (arguments[argc - 1] == 'GMT');\n" + "\n" + " if (isGMT) {\n" + " argc--;\n" + " }\n" + " // function will work even without explict handling of this case\n" + " if (argc == 1) {\n" + " var tmp = parseInt(arguments[0]);\n" + " if (isNaN(tmp)) {\n" + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" + " getMonth(arguments[0]));\n" + " } else if (tmp < 32) {\n" + " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" + " } else { \n" + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" + " tmp);\n" + " }\n" + " }\n" + " var year = date.getFullYear();\n" + " var date1, date2;\n" + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" + " var adjustMonth = false;\n" + " for (var i = 0; i < (argc >> 1); i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date1.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " adjustMonth = (argc <= 2);\n" + " date1.setDate(tmp);\n" + " } else {\n" + " date1.setFullYear(tmp);\n" + " }\n" + " }\n" + " for (var i = (argc >> 1); i < argc; i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date2.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " date2.setDate(tmp);\n" + " } else {\n" + " date2.setFullYear(tmp);\n" + " }\n" + " }\n" + " if (adjustMonth) {\n" + " date1.setMonth(date.getMonth());\n" + " date2.setMonth(date.getMonth());\n" + " }\n" + " if (isGMT) {\n" + " var tmp = date;\n" + " tmp.setFullYear(date.getUTCFullYear());\n" + " tmp.setMonth(date.getUTCMonth());\n" + " tmp.setDate(date.getUTCDate());\n" + " tmp.setHours(date.getUTCHours());\n" + " tmp.setMinutes(date.getUTCMinutes());\n" + " tmp.setSeconds(date.getUTCSeconds());\n" + " date = tmp;\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "}\n" + "" + "function timeRange() {\n" + " var argc = arguments.length;\n" + " var date = new Date();\n" + " var isGMT= false;\n" + "" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " isGMT = true;\n" + " argc--;\n" + " }\n" + "\n" + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" + " var date1, date2;\n" + " date1 = new Date();\n" + " date2 = new Date();\n" + "\n" + " if (argc == 1) {\n" + " return (hour == arguments[0]);\n" + " } else if (argc == 2) {\n" + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" + " } else {\n" + " switch (argc) {\n" + " case 6:\n" + " date1.setSeconds(arguments[2]);\n" + " date2.setSeconds(arguments[5]);\n" + " case 4:\n" + " var middle = argc >> 1;\n" + " date1.setHours(arguments[0]);\n" + " date1.setMinutes(arguments[1]);\n" + " date2.setHours(arguments[middle]);\n" + " date2.setMinutes(arguments[middle + 1]);\n" + " if (middle == 2) {\n" + " date2.setSeconds(59);\n" + " }\n" + " break;\n" + " default:\n" + " throw 'timeRange: bad number of arguments'\n" + " }\n" + " }\n" + "\n" + " if (isGMT) {\n" + " date.setFullYear(date.getUTCFullYear());\n" + " date.setMonth(date.getUTCMonth());\n" + " date.setDate(date.getUTCDate());\n" + " date.setHours(date.getUTCHours());\n" + " date.setMinutes(date.getUTCMinutes());\n" + " date.setSeconds(date.getUTCSeconds());\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "\n" + "}\n" + ""; + +// sRunning is defined for the helper functions only while the +// Javascript engine is running and the PAC object cannot be deleted +// or reset. +static uint32_t sRunningIndex = 0xdeadbeef; +static ProxyAutoConfig *GetRunning() +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + return static_cast(PR_GetThreadPrivate(sRunningIndex)); +} + +static void SetRunning(ProxyAutoConfig *arg) +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + PR_SetThreadPrivate(sRunningIndex, arg); +} + +// The PACResolver is used for dnsResolve() +class PACResolver final : public nsIDNSListener + , public nsITimerCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + PACResolver() + : mStatus(NS_ERROR_FAILURE) + { + } + + // nsIDNSListener + NS_IMETHOD OnLookupComplete(nsICancelable *request, + nsIDNSRecord *record, + nsresult status) override + { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + mRequest = nullptr; + mStatus = status; + mResponse = record; + return NS_OK; + } + + // nsITimerCallback + NS_IMETHOD Notify(nsITimer *timer) override + { + if (mRequest) + mRequest->Cancel(NS_ERROR_NET_TIMEOUT); + mTimer = nullptr; + return NS_OK; + } + + nsresult mStatus; + nsCOMPtr mRequest; + nsCOMPtr mResponse; + nsCOMPtr mTimer; + +private: + ~PACResolver() {} +}; +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback) + +static +void PACLogToConsole(nsString &aMessage) +{ + 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(NS_LITERAL_STRING("PAC Execution ")); + formattedMessage += aKind; + formattedMessage += NS_LITERAL_STRING(": "); + if (aReport->message()) + formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + formattedMessage += NS_LITERAL_STRING(" ["); + formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); + formattedMessage += NS_LITERAL_STRING("]"); + PACLogToConsole(formattedMessage); +} + +static void +PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) +{ + MOZ_ASSERT(aReport); + MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags)); + + PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport); +} + +class MOZ_STACK_CLASS AutoPACErrorReporter +{ + JSContext* mCx; + +public: + explicit AutoPACErrorReporter(JSContext* aCx) + : mCx(aCx) + {} + ~AutoPACErrorReporter() { + if (!JS_IsExceptionPending(mCx)) { + return; + } + JS::RootedValue exn(mCx); + if (!JS_GetPendingException(mCx, &exn)) { + return; + } + JS_ClearPendingException(mCx); + + js::ErrorReport report(mCx); + if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) { + JS_ClearPendingException(mCx); + return; + } + + PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report()); + } +}; + +// timeout of 0 means the normal necko timeout strategy, otherwise the dns request +// will be canceled after aTimeout milliseconds +static +bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr, + unsigned int aTimeout) +{ + if (!GetRunning()) { + NS_WARNING("PACResolve without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout); +} + +ProxyAutoConfig::ProxyAutoConfig() + : mJSContext(nullptr) + , mJSNeedsSetup(false) + , mShutdown(false) + , mIncludePath(false) +{ + MOZ_COUNT_CTOR(ProxyAutoConfig); +} + +bool +ProxyAutoConfig::ResolveAddress(const nsCString &aHostName, + NetAddr *aNetAddr, + unsigned int aTimeout) +{ + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) + return false; + + RefPtr helper = new PACResolver(); + + if (NS_FAILED(dns->AsyncResolve(aHostName, + nsIDNSService::RESOLVE_PRIORITY_MEDIUM, + helper, + NS_GetCurrentThread(), + getter_AddRefs(helper->mRequest)))) + return false; + + if (aTimeout && helper->mRequest) { + if (!mTimer) + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (mTimer) { + mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT); + helper->mTimer = mTimer; + } + } + + // Spin the event loop of the pac thread until lookup is complete. + // nsPACman is responsible for keeping a queue and only allowing + // one PAC execution at a time even when it is called re-entrantly. + while (helper->mRequest) + NS_ProcessNextEvent(NS_GetCurrentThread()); + + if (NS_FAILED(helper->mStatus) || + NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr))) + return false; + return true; +} + +static +bool PACResolveToString(const nsCString &aHostName, + nsCString &aDottedDecimal, + unsigned int aTimeout) +{ + NetAddr netAddr; + if (!PACResolve(aHostName, &netAddr, aTimeout)) + return false; + + char dottedDecimal[128]; + if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal))) + return false; + + aDottedDecimal.Assign(dottedDecimal); + return true; +} + +// dnsResolve(host) javascript implementation +static +bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!args.requireAtLeast(cx, "dnsResolve", 1)) + return false; + + JS::Rooted arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) + return false; + + nsAutoJSString hostName; + nsAutoCString dottedDecimal; + + if (!hostName.init(cx, arg1)) + return false; + if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) { + JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + args.rval().setString(dottedDecimalString); + } + else { + args.rval().setNull(); + } + + return true; +} + +// myIpAddress() javascript implementation +static +bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!GetRunning()) { + NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->MyIPAddress(args); +} + +// proxyAlert(msg) javascript implementation +static +bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "alert", 1)) + return false; + + JS::Rooted arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) + return false; + + nsAutoJSString message; + if (!message.init(cx, arg1)) + return false; + + nsAutoString alertMessage; + alertMessage.SetCapacity(32 + message.Length()); + alertMessage += NS_LITERAL_STRING("PAC-alert: "); + alertMessage += message; + PACLogToConsole(alertMessage); + + args.rval().setUndefined(); /* return undefined */ + return true; +} + +static const JSFunctionSpec PACGlobalFunctions[] = { + JS_FS("dnsResolve", PACDnsResolve, 1, 0), + + // a global "var pacUseMultihomedDNS = true;" will change behavior + // of myIpAddress to actively use DNS + JS_FS("myIpAddress", PACMyIpAddress, 0, 0), + JS_FS("alert", PACProxyAlert, 1, 0), + JS_FS_END +}; + +// JSContextWrapper is a c++ object that manages the context for the JS engine +// used on the PAC thread. It is initialized and destroyed on the PAC thread. +class JSContextWrapper +{ + public: + static JSContextWrapper *Create() + { + JSContext* cx = JS_NewContext(sContextHeapSize); + if (NS_WARN_IF(!cx)) + return nullptr; + + JSContextWrapper *entry = new JSContextWrapper(cx); + if (NS_FAILED(entry->Init())) { + delete entry; + return nullptr; + } + + return entry; + } + + JSContext *Context() const + { + return mContext; + } + + JSObject *Global() const + { + return mGlobal; + } + + ~JSContextWrapper() + { + mGlobal = nullptr; + + MOZ_COUNT_DTOR(JSContextWrapper); + + if (mContext) { + JS_DestroyContext(mContext); + } + } + + void SetOK() + { + mOK = true; + } + + bool IsOK() + { + return mOK; + } + +private: + static const unsigned sContextHeapSize = 4 << 20; // 4 MB + + JSContext *mContext; + JS::PersistentRooted mGlobal; + bool mOK; + + static const JSClass sGlobalClass; + + explicit JSContextWrapper(JSContext* cx) + : mContext(cx), mGlobal(cx, nullptr), mOK(false) + { + MOZ_COUNT_CTOR(JSContextWrapper); + } + + nsresult Init() + { + /* + * Not setting this will cause JS_CHECK_RECURSION to report false + * positives + */ + JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024); + + JS::SetWarningReporter(mContext, PACWarningReporter); + + if (!JS::InitSelfHostedCode(mContext)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + JSAutoRequest ar(mContext); + + JS::CompartmentOptions options; + options.creationOptions().setZone(JS::SystemZone); + options.behaviors().setVersion(JSVERSION_LATEST); + mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, + JS::DontFireOnNewGlobalHook, options); + if (!mGlobal) { + JS_ClearPendingException(mContext); + return NS_ERROR_OUT_OF_MEMORY; + } + JS::Rooted global(mContext, mGlobal); + + JSAutoCompartment ac(mContext, global); + AutoPACErrorReporter aper(mContext); + if (!JS_InitStandardClasses(mContext, global)) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { + return NS_ERROR_FAILURE; + } + + JS_FireOnNewGlobalObject(mContext, global); + + return NS_OK; + } +}; + +static const JSClassOps sJSContextWrapperGlobalClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook +}; + +const JSClass JSContextWrapper::sGlobalClass = { + "PACResolutionThreadGlobal", + JSCLASS_GLOBAL_FLAGS, + &sJSContextWrapperGlobalClassOps +}; + +void +ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) +{ + sRunningIndex = index; +} + +nsresult +ProxyAutoConfig::Init(const nsCString &aPACURI, + const nsCString &aPACScript, + bool aIncludePath) +{ + mPACURI = aPACURI; + mPACScript = sPacUtils; + mPACScript.Append(aPACScript); + mIncludePath = aIncludePath; + + if (!GetRunning()) + return SetupJS(); + + mJSNeedsSetup = true; + return NS_OK; +} + +nsresult +ProxyAutoConfig::SetupJS() +{ + mJSNeedsSetup = false; + MOZ_ASSERT(!GetRunning(), "JIT is running"); + + delete mJSContext; + mJSContext = nullptr; + + if (mPACScript.IsEmpty()) + return NS_ERROR_FAILURE; + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + mJSContext = JSContextWrapper::Create(); + if (!mJSContext) + return NS_ERROR_FAILURE; + + JSContext* cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // check if this is a data: uri so that we don't spam the js console with + // huge meaningless strings. this is not on the main thread, so it can't + // use nsIURI scheme methods + bool isDataURI = nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5); + + SetRunning(this); + JS::Rooted global(cx, mJSContext->Global()); + JS::CompileOptions options(cx); + options.setFileAndLine(mPACURI.get(), 1); + JS::Rooted script(cx); + if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options, + &script) || + !JS_ExecuteScript(cx, script)) + { + nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + SetRunning(nullptr); + return NS_ERROR_FAILURE; + } + SetRunning(nullptr); + + mJSContext->SetOK(); + nsString alertMessage(NS_LITERAL_STRING("PAC file installed from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + + // we don't need these now + mPACScript.Truncate(); + mPACURI.Truncate(); + + return NS_OK; +} + +nsresult +ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI, + const nsCString &aTestHost, + nsACString &result) +{ + if (mJSNeedsSetup) + SetupJS(); + + if (!mJSContext || !mJSContext->IsOK()) + return NS_ERROR_NOT_AVAILABLE; + + JSContext *cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // the sRunning flag keeps a new PAC file from being installed + // while the event loop is spinning on a DNS function. Don't early return. + SetRunning(this); + mRunningHost = aTestHost; + + nsresult rv = NS_ERROR_FAILURE; + nsCString clensedURI = aTestURI; + + if (!mIncludePath) { + nsCOMPtr urlParser = + do_GetService(NS_STDURLPARSER_CONTRACTID); + int32_t pathLen = 0; + if (urlParser) { + uint32_t schemePos; + int32_t schemeLen; + uint32_t authorityPos; + int32_t authorityLen; + uint32_t pathPos; + rv = urlParser->ParseURL(aTestURI.get(), aTestURI.Length(), + &schemePos, &schemeLen, + &authorityPos, &authorityLen, + &pathPos, &pathLen); + } + if (NS_SUCCEEDED(rv)) { + if (pathLen) { + // cut off the path but leave the initial slash + pathLen--; + } + aTestURI.Left(clensedURI, aTestURI.Length() - pathLen); + } + } + + JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get())); + JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get())); + + if (uriString && hostString) { + JS::AutoValueArray<2> args(cx); + args[0].setString(uriString); + args[1].setString(hostString); + + JS::Rooted 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; + + JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global()); + JS_MaybeGC(mJSContext->Context()); +} + +ProxyAutoConfig::~ProxyAutoConfig() +{ + MOZ_COUNT_DTOR(ProxyAutoConfig); + NS_ASSERTION(!mJSContext, + "~ProxyAutoConfig leaking JS context that " + "should have been deleted on pac thread"); +} + +void +ProxyAutoConfig::Shutdown() +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown"); + + if (GetRunning() || mShutdown) + return; + + mShutdown = true; + delete mJSContext; + mJSContext = nullptr; +} + +bool +ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress) +{ + PRFileDesc *fd; + fd = PR_OpenUDPSocket(remoteAddress->raw.family); + if (!fd) + return false; + + PRNetAddr prRemoteAddress; + NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress); + if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PRNetAddr localName; + if (PR_GetSockName(fd, &localName) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PR_Close(fd); + + char dottedDecimal[128]; + if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS) + return false; + + localAddress.Assign(dottedDecimal); + + return true; +} + +// hostName is run through a dns lookup and then a udp socket is connected +// to the result. If that all works, the local IP address of the socket is +// returned to the javascript caller and |*aResult| is set to true. Otherwise +// |*aResult| is set to false. +bool +ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName, + unsigned int timeout, + const JS::CallArgs &aArgs, + bool* aResult) +{ + *aResult = false; + + NetAddr remoteAddress; + nsAutoCString localDottedDecimal; + JSContext *cx = mJSContext->Context(); + + if (PACResolve(hostName, &remoteAddress, timeout) && + SrcAddress(&remoteAddress, localDottedDecimal)) { + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + *aResult = true; + aArgs.rval().setString(dottedDecimalString); + } + return true; +} + +bool +ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs) +{ + nsAutoCString remoteDottedDecimal; + nsAutoCString localDottedDecimal; + JSContext *cx = mJSContext->Context(); + JS::RootedValue v(cx); + JS::Rooted global(cx, mJSContext->Global()); + + bool useMultihomedDNS = + JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) && + !v.isUndefined() && ToBoolean(v); + + // first, lookup the local address of a socket connected + // to the host of uri being resolved by the pac file. This is + // v6 safe.. but is the last step like that + bool rvalAssigned = false; + if (useMultihomedDNS) { + if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + } else { + // we can still do the fancy multi homing thing if the host is a literal + PRNetAddr tempAddr; + memset(&tempAddr, 0, sizeof(PRNetAddr)); + if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) && + (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned)) { + return rvalAssigned; + } + } + + // next, look for a route to a public internet address that doesn't need DNS. + // This is the google anycast dns address, but it doesn't matter if it + // remains operable (as we don't contact it) as long as the address stays + // in commonly routed IP address space. + remoteDottedDecimal.AssignLiteral("8.8.8.8"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // finally, use the old algorithm based on the local hostname + nsAutoCString hostName; + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + // without multihomedDNS use such a short timeout that we are basically + // just looking at the cache for raw dotted decimals + uint32_t timeout = useMultihomedDNS ? kTimeout : 1; + if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) && + PACResolveToString(hostName, localDottedDecimal, timeout)) { + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; + } + + // next try a couple RFC 1918 variants.. maybe there is a + // local route + remoteDottedDecimal.AssignLiteral("192.168.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // more RFC 1918 + remoteDottedDecimal.AssignLiteral("10.0.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // who knows? let's fallback to localhost + localDottedDecimal.AssignLiteral("127.0.0.1"); + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h new file mode 100644 index 000000000..1f7b88ac6 --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ProxyAutoConfig_h__ +#define ProxyAutoConfig_h__ + +#include "nsString.h" +#include "nsCOMPtr.h" + +class nsITimer; +namespace JS { +class CallArgs; +} // namespace JS + +namespace mozilla { namespace net { + +class JSContextWrapper; +union NetAddr; + +// The ProxyAutoConfig class is meant to be created and run on a +// non main thread. It synchronously resolves PAC files by blocking that +// thread and running nested event loops. GetProxyForURI is not re-entrant. + +class ProxyAutoConfig { +public: + ProxyAutoConfig(); + ~ProxyAutoConfig(); + + nsresult Init(const nsCString &aPACURI, + const nsCString &aPACScript, + bool aIncludePath); + void SetThreadLocalIndex(uint32_t index); + void Shutdown(); + void GC(); + bool MyIPAddress(const JS::CallArgs &aArgs); + bool ResolveAddress(const nsCString &aHostName, + NetAddr *aNetAddr, unsigned int aTimeout); + + /** + * Get the proxy string for the specified URI. The proxy string is + * given by the following: + * + * result = proxy-spec *( proxy-sep proxy-spec ) + * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port] + * direct-type = "DIRECT" + * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5" + * proxy-sep = ";" LWS + * proxy-host = hostname | ipv4-address-literal + * proxy-port = + * 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 nsCString &aTestURI, + const nsCString &aTestHost, + nsACString &result); + +private: + // allow 665ms for myipaddress dns queries. That's 95th percentile. + const static unsigned int kTimeout = 665; + + // used to compile the PAC file and setup the execution context + nsresult SetupJS(); + + bool SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress); + bool MyIPAddressTryHost(const nsCString &hostName, unsigned int timeout, + const JS::CallArgs &aArgs, bool* aResult); + + JSContextWrapper *mJSContext; + bool mJSNeedsSetup; + bool mShutdown; + nsCString mPACScript; + nsCString mPACURI; + bool mIncludePath; + nsCString mRunningHost; + nsCOMPtr mTimer; +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfig_h__ diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp new file mode 100644 index 000000000..17e26603a --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.cpp @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RedirectChannelRegistrar.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar) + +RedirectChannelRegistrar::RedirectChannelRegistrar() + : mRealChannels(32) + , mParentChannels(32) + , mId(1) + , mLock("RedirectChannelRegistrar") +{ +} + +NS_IMETHODIMP +RedirectChannelRegistrar::RegisterChannel(nsIChannel *channel, + uint32_t *_retval) +{ + MutexAutoLock lock(mLock); + + mRealChannels.Put(mId, channel); + *_retval = mId; + + ++mId; + + // Ensure we always provide positive ids + if (!mId) + mId = 1; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetRegisteredChannel(uint32_t id, + nsIChannel **_retval) +{ + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::LinkChannels(uint32_t id, + nsIParentChannel *channel, + nsIChannel** _retval) +{ + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + mParentChannels.Put(id, channel); + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetParentChannel(uint32_t id, + nsIParentChannel **_retval) +{ + MutexAutoLock lock(mLock); + + if (!mParentChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::DeregisterChannels(uint32_t id) +{ + MutexAutoLock lock(mLock); + + mRealChannels.Remove(id); + mParentChannels.Remove(id); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h new file mode 100644 index 000000000..46e46c264 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RedirectChannelRegistrar_h__ +#define RedirectChannelRegistrar_h__ + +#include "nsIRedirectChannelRegistrar.h" + +#include "nsIChannel.h" +#include "nsIParentChannel.h" +#include "nsInterfaceHashtable.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIREDIRECTCHANNELREGISTRAR + + RedirectChannelRegistrar(); + +private: + ~RedirectChannelRegistrar() {} + +protected: + typedef nsInterfaceHashtable + ChannelHashtable; + typedef nsInterfaceHashtable + ParentChannelHashtable; + + ChannelHashtable mRealChannels; + ParentChannelHashtable mParentChannels; + uint32_t mId; + Mutex mLock; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h new file mode 100644 index 000000000..591b9daf0 --- /dev/null +++ b/netwerk/base/ReferrerPolicy.h @@ -0,0 +1,186 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ReferrerPolicy_h__ +#define ReferrerPolicy_h__ + +#include "nsStringGlue.h" +#include "nsIHttpChannel.h" +#include "nsUnicharUtils.h" + +namespace mozilla { namespace net { + +enum ReferrerPolicy { + /* spec tokens: never no-referrer */ + RP_No_Referrer = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER, + + /* spec tokens: origin */ + RP_Origin = nsIHttpChannel::REFERRER_POLICY_ORIGIN, + + /* spec tokens: default no-referrer-when-downgrade */ + RP_No_Referrer_When_Downgrade = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, + RP_Default = nsIHttpChannel::REFERRER_POLICY_DEFAULT, + + /* spec tokens: origin-when-cross-origin */ + RP_Origin_When_Crossorigin = nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN, + + /* spec tokens: always unsafe-url */ + RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL, + + /* spec tokens: same-origin */ + RP_Same_Origin = nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN, + + /* spec tokens: strict-origin */ + RP_Strict_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN, + + /* spec tokens: strict-origin-when-cross-origin */ + RP_Strict_Origin_When_Cross_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN, + + /* spec tokens: empty string */ + /* The empty string "" corresponds to no referrer policy, or unset policy */ + RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET, +}; + +/* spec tokens: never no-referrer */ +const char kRPS_Never[] = "never"; +const char kRPS_No_Referrer[] = "no-referrer"; + +/* spec tokens: origin */ +const char kRPS_Origin[] = "origin"; + +/* spec tokens: default no-referrer-when-downgrade */ +const char kRPS_Default[] = "default"; +const char kRPS_No_Referrer_When_Downgrade[] = "no-referrer-when-downgrade"; + +/* spec tokens: origin-when-cross-origin */ +const char kRPS_Origin_When_Cross_Origin[] = "origin-when-cross-origin"; +const char kRPS_Origin_When_Crossorigin[] = "origin-when-crossorigin"; + +/* spec tokens: same-origin */ +const char kRPS_Same_Origin[] = "same-origin"; + +/* spec tokens: strict-origin */ +const char kRPS_Strict_Origin[] = "strict-origin"; + +/* spec tokens: strict-origin-when-cross-origin */ +const char kRPS_Strict_Origin_When_Cross_Origin[] = "strict-origin-when-cross-origin"; + +/* spec tokens: always unsafe-url */ +const char kRPS_Always[] = "always"; +const char kRPS_Unsafe_URL[] = "unsafe-url"; + +inline ReferrerPolicy +ReferrerPolicyFromString(const nsAString& content) +{ + if (content.IsEmpty()) { + return RP_No_Referrer; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + // This is implemented step by step as described in the Referrer Policy + // specification, section "Determine token's Policy". + if (lowerContent.EqualsLiteral(kRPS_Never) || + lowerContent.EqualsLiteral(kRPS_No_Referrer)) { + return RP_No_Referrer; + } + if (lowerContent.EqualsLiteral(kRPS_Origin)) { + return RP_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Default) || + lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) { + return RP_No_Referrer_When_Downgrade; + } + if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) || + lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)) { + return RP_Origin_When_Crossorigin; + } + if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) { + return RP_Same_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) { + return RP_Strict_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) { + return RP_Strict_Origin_When_Cross_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Always) || + lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) { + return RP_Unsafe_URL; + } + // Spec says if none of the previous match, use empty string. + return RP_Unset; + +} + +inline bool +IsValidReferrerPolicy(const nsAString& content) +{ + if (content.IsEmpty()) { + return true; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + + return lowerContent.EqualsLiteral(kRPS_Never) + || lowerContent.EqualsLiteral(kRPS_No_Referrer) + || lowerContent.EqualsLiteral(kRPS_Origin) + || lowerContent.EqualsLiteral(kRPS_Default) + || lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade) + || lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) + || lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin) + || lowerContent.EqualsLiteral(kRPS_Same_Origin) + || lowerContent.EqualsLiteral(kRPS_Strict_Origin) + || lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin) + || lowerContent.EqualsLiteral(kRPS_Always) + || lowerContent.EqualsLiteral(kRPS_Unsafe_URL); +} + +inline ReferrerPolicy +AttributeReferrerPolicyFromString(const nsAString& content) +{ + // Specs : https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute + // Spec says the empty string "" corresponds to no referrer policy, or RP_Unset + if (content.IsEmpty()) { + return RP_Unset; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + + if (lowerContent.EqualsLiteral(kRPS_No_Referrer)) { + return RP_No_Referrer; + } + if (lowerContent.EqualsLiteral(kRPS_Origin)) { + return RP_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) { + return RP_No_Referrer_When_Downgrade; + } + if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)) { + return RP_Origin_When_Crossorigin; + } + if (lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) { + return RP_Unsafe_URL; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) { + return RP_Strict_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) { + return RP_Same_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) { + return RP_Strict_Origin_When_Cross_Origin; + } + + // Spec says invalid value default is empty string state + // So, return RP_Unset if none of the previous match, return RP_Unset + return RP_Unset; +} + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp new file mode 100644 index 000000000..b72e42b13 --- /dev/null +++ b/netwerk/base/RequestContextService.cpp @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAutoPtr.h" +#include "nsIObserverService.h" +#include "nsIUUIDGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "RequestContextService.h" + +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" + +#include "mozilla/net/PSpdyPush.h" + +namespace mozilla { +namespace net { + +// nsIRequestContext +class RequestContext final : public nsIRequestContext +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXT + + explicit RequestContext(const nsID& id); +private: + virtual ~RequestContext(); + + nsID mID; + char mCID[NSID_LENGTH]; + Atomic mBlockingTransactionCount; + nsAutoPtr mSpdyCache; + nsCString mUserAgentOverride; +}; + +NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext) + +RequestContext::RequestContext(const nsID& aID) + : mBlockingTransactionCount(0) +{ + mID = aID; + mID.ToProvidedString(mCID); +} + +RequestContext::~RequestContext() +{ +} + +NS_IMETHODIMP +RequestContext::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount) +{ + NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); + *aBlockingTransactionCount = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::AddBlockingTransaction() +{ + mBlockingTransactionCount++; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveBlockingTransaction(uint32_t *outval) +{ + NS_ENSURE_ARG_POINTER(outval); + mBlockingTransactionCount--; + *outval = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) +{ + *aSpdyPushCache = mSpdyCache.get(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) +{ + mSpdyCache = aSpdyPushCache; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetID(nsID *outval) +{ + NS_ENSURE_ARG_POINTER(outval); + *outval = mID; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetUserAgentOverride(nsACString& aUserAgentOverride) +{ + aUserAgentOverride = mUserAgentOverride; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::SetUserAgentOverride(const nsACString& aUserAgentOverride) +{ + mUserAgentOverride = aUserAgentOverride; + return NS_OK; +} + + +//nsIRequestContextService +RequestContextService *RequestContextService::sSelf = nullptr; + +NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver) + +RequestContextService::RequestContextService() +{ + MOZ_ASSERT(!sSelf, "multiple rcs instances!"); + MOZ_ASSERT(NS_IsMainThread()); + sSelf = this; +} + +RequestContextService::~RequestContextService() +{ + MOZ_ASSERT(NS_IsMainThread()); + Shutdown(); + sSelf = nullptr; +} + +nsresult +RequestContextService::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); +} + +void +RequestContextService::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + mTable.Clear(); +} + +/* static */ nsresult +RequestContextService::Create(nsISupports *aOuter, const nsIID& aIID, void **aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr svc = new RequestContextService(); + nsresult rv = svc->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return svc->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +RequestContextService::GetRequestContext(const nsID& rcID, nsIRequestContext **rc) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (!mTable.Get(rcID, rc)) { + nsCOMPtr newSC = new RequestContext(rcID); + mTable.Put(rcID, newSC); + newSC.swap(*rc); + } + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::NewRequestContextID(nsID *rcID) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!mUUIDGen) { + nsresult rv; + mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mUUIDGen->GenerateUUIDInPlace(rcID); +} + +NS_IMETHODIMP +RequestContextService::RemoveRequestContext(const nsID& rcID) +{ + MOZ_ASSERT(NS_IsMainThread()); + mTable.Remove(rcID); + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::Observe(nsISupports *subject, const char *topic, + const char16_t *data_unicode) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + } + + return NS_OK; +} + +} // ::mozilla::net +} // ::mozilla diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h new file mode 100644 index 000000000..d7b8e971a --- /dev/null +++ b/netwerk/base/RequestContextService.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla__net__RequestContextService_h +#define mozilla__net__RequestContextService_h + +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsIObserver.h" +#include "nsIRequestContext.h" + +class nsIUUIDGenerator; + +namespace mozilla { +namespace net { + +class RequestContextService final + : public nsIRequestContextService + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXTSERVICE + NS_DECL_NSIOBSERVER + + RequestContextService(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(nsISupports *outer, const nsIID& iid, void **result); + +private: + virtual ~RequestContextService(); + + static RequestContextService *sSelf; + + nsInterfaceHashtable mTable; + nsCOMPtr mUUIDGen; +}; + +} // ::mozilla::net +} // ::mozilla + +#endif // mozilla__net__RequestContextService_h diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp new file mode 100644 index 000000000..65d792459 --- /dev/null +++ b/netwerk/base/ShutdownLayer.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "ShutdownLayer.h" +#include "prerror.h" +#include "private/pprio.h" +#include "prmem.h" +#include + +static PRDescIdentity sWinSockShutdownLayerIdentity; +static PRIOMethods sWinSockShutdownLayerMethods; +static PRIOMethods *sWinSockShutdownLayerMethodsPtr = nullptr; + +namespace mozilla { +namespace net { + +extern PRDescIdentity nsNamedPipeLayerIdentity; + +} // namespace net +} // namespace mozilla + + +PRStatus +WinSockClose(PRFileDesc *aFd) +{ + MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity, + "Windows shutdown layer not on the top of the stack"); + + PROsfd osfd = PR_FileDesc2NativeHandle(aFd); + if (osfd != -1) { + shutdown(osfd, SD_BOTH); + } + + aFd->identity = PR_INVALID_IO_LAYER; + + if (aFd->lower) { + return aFd->lower->methods->close(aFd->lower); + } else { + return PR_SUCCESS; + } +} + +nsresult mozilla::net::AttachShutdownLayer(PRFileDesc *aFd) +{ + if (!sWinSockShutdownLayerMethodsPtr) { + sWinSockShutdownLayerIdentity = + PR_GetUniqueIdentity("windows shutdown call layer"); + sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods(); + sWinSockShutdownLayerMethods.close = WinSockClose; + sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods; + } + + if (mozilla::net::nsNamedPipeLayerIdentity && + PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) { + // Do not attach shutdown layer on named pipe layer, + // it is for PR_NSPR_IO_LAYER only. + return NS_OK; + } + + PRFileDesc * layer; + PRStatus status; + + layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity, + sWinSockShutdownLayerMethodsPtr); + if (!layer) { + return NS_OK; + } + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_DELETE(layer); + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h new file mode 100644 index 000000000..59843b7d0 --- /dev/null +++ b/netwerk/base/ShutdownLayer.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SHUTDOWNLAYER_H___ +#define SHUTDOWNLAYER_H___ + +#include "nscore.h" +#include "prio.h" + +namespace mozilla { namespace net { + +// This is only for windows. This layer will be attached jus before PR_CLose +// is call and it will only call shutdown(sock). +extern nsresult AttachShutdownLayer(PRFileDesc *fd); + +} // namespace net +} // namespace mozilla + +#endif /* SHUTDOWN_H___ */ diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp new file mode 100644 index 000000000..d0e311f77 --- /dev/null +++ b/netwerk/base/SimpleBuffer.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleBuffer.h" +#include + +namespace mozilla { +namespace net { + +SimpleBuffer::SimpleBuffer() + : mStatus(NS_OK) + , mAvailable(0) +{ + mOwningThread = PR_GetCurrentThread(); +} + +nsresult SimpleBuffer::Write(char *src, size_t len) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + while (len > 0) { + SimpleBufferPage *p = mBufferList.getLast(); + if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) { + // no room.. make a new page + p = nullptr; + } + if (!p) { + p = new (fallible) SimpleBufferPage(); + if (!p) { + mStatus = NS_ERROR_OUT_OF_MEMORY; + return mStatus; + } + mBufferList.insertBack(p); + } + size_t roomOnPage = SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset; + size_t toWrite = std::min(roomOnPage, len); + memcpy(p->mBuffer + p->mWriteOffset, src, toWrite); + src += toWrite; + len -= toWrite; + p->mWriteOffset += toWrite; + mAvailable += toWrite; + } + return NS_OK; +} + +size_t SimpleBuffer::Read(char *dest, size_t maxLen) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + if (NS_FAILED(mStatus)) { + return 0; + } + + size_t rv = 0; + for (SimpleBufferPage *p = mBufferList.getFirst(); + p && (rv < maxLen); p = mBufferList.getFirst()) { + size_t avail = p->mWriteOffset - p->mReadOffset; + size_t toRead = std::min(avail, (maxLen - rv)); + memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead); + rv += toRead; + p->mReadOffset += toRead; + if (p->mReadOffset == p->mWriteOffset) { + p->remove(); + delete p; + } + } + + MOZ_ASSERT(mAvailable >= rv); + mAvailable -= rv; + return rv; +} + +size_t SimpleBuffer::Available() +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + return NS_SUCCEEDED(mStatus) ? mAvailable : 0; +} + +void SimpleBuffer::Clear() +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + SimpleBufferPage *p; + while ((p = mBufferList.popFirst())) { + delete p; + } + mAvailable = 0; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h new file mode 100644 index 000000000..765239001 --- /dev/null +++ b/netwerk/base/SimpleBuffer.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SimpleBuffer_h__ +#define SimpleBuffer_h__ + +/* + This class is similar to a nsPipe except it does not have any locking, stores + an unbounded amount of data, can only be used on one thread, and has much + simpler result code semantics to deal with. +*/ + +#include "prtypes.h" +#include "mozilla/LinkedList.h" +#include "nsIThreadInternal.h" + +namespace mozilla { +namespace net { + +class SimpleBufferPage : public LinkedListElement +{ +public: + SimpleBufferPage() : mReadOffset(0), mWriteOffset(0) {} + static const size_t kSimpleBufferPageSize = 32000; + +private: + friend class SimpleBuffer; + char mBuffer[kSimpleBufferPageSize]; + size_t mReadOffset; + size_t mWriteOffset; +}; + +class SimpleBuffer +{ +public: + SimpleBuffer(); + ~SimpleBuffer() {} + + nsresult Write(char *stc, size_t len); // return OK or OUT_OF_MEMORY + size_t Read(char *dest, size_t maxLen); // return bytes read + size_t Available(); + void Clear(); + +private: + PRThread *mOwningThread; + nsresult mStatus; + AutoCleanLinkedList mBufferList; + size_t mAvailable; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/StreamingProtocolService.cpp b/netwerk/base/StreamingProtocolService.cpp new file mode 100644 index 000000000..3df3326bc --- /dev/null +++ b/netwerk/base/StreamingProtocolService.cpp @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" +#include "mozilla/ClearOnShutdown.h" +#include "StreamingProtocolService.h" +#include "mozilla/net/NeckoChild.h" +#include "nsIURI.h" +#include "necko-config.h" + +#ifdef NECKO_PROTOCOL_rtsp +#include "RtspControllerChild.h" +#include "RtspController.h" +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(StreamingProtocolControllerService, + nsIStreamingProtocolControllerService) + +/* static */ +StaticRefPtr sSingleton; + +/* static */ +already_AddRefed +StreamingProtocolControllerService::GetInstance() +{ + if (!sSingleton) { + sSingleton = new StreamingProtocolControllerService(); + ClearOnShutdown(&sSingleton); + } + RefPtr service = sSingleton.get(); + return service.forget(); +} + +NS_IMETHODIMP +StreamingProtocolControllerService::Create(nsIChannel *aChannel, nsIStreamingProtocolController **aResult) +{ + RefPtr mediacontroller; + nsCOMPtr uri; + nsAutoCString scheme; + + NS_ENSURE_ARG_POINTER(aChannel); + aChannel->GetURI(getter_AddRefs(uri)); + + nsresult rv = uri->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + +#ifdef NECKO_PROTOCOL_rtsp + if (scheme.EqualsLiteral("rtsp")) { + if (IsNeckoChild()) { + mediacontroller = new RtspControllerChild(aChannel); + } else { + mediacontroller = new RtspController(aChannel); + } + } +#endif + + if (!mediacontroller) { + return NS_ERROR_NO_INTERFACE; + } + + mediacontroller->Init(uri); + + mediacontroller.forget(aResult); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/StreamingProtocolService.h b/netwerk/base/StreamingProtocolService.h new file mode 100644 index 000000000..96d51fe5c --- /dev/null +++ b/netwerk/base/StreamingProtocolService.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_StreamingProtocolControllerService_h +#define mozilla_net_StreamingProtocolControllerService_h + +#include "mozilla/StaticPtr.h" +#include "nsIStreamingProtocolService.h" +#include "nsIStreamingProtocolController.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +/** + * This class implements a service to help to create streaming protocol controller. + */ +class StreamingProtocolControllerService : public nsIStreamingProtocolControllerService +{ +private: + virtual ~StreamingProtocolControllerService() {}; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLCONTROLLERSERVICE + + StreamingProtocolControllerService() {}; + static already_AddRefed GetInstance(); +}; + +} // namespace net +} // namespace mozilla + +#endif //mozilla_net_StreamingProtocolControllerService_h diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp new file mode 100644 index 000000000..b32a9a188 --- /dev/null +++ b/netwerk/base/TLSServerSocket.cpp @@ -0,0 +1,515 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TLSServerSocket.h" + +#include "mozilla/net/DNS.h" +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIServerSocket.h" +#include "nsITimer.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsNetCID.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransport2.h" +#include "nsThreadUtils.h" +#include "ScopedNSSTypes.h" +#include "ssl.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// TLSServerSocket +//----------------------------------------------------------------------------- + +TLSServerSocket::TLSServerSocket() + : mServerCert(nullptr) +{ +} + +TLSServerSocket::~TLSServerSocket() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket, + nsServerSocket, + nsITLSServerSocket) + +nsresult +TLSServerSocket::SetSocketDefaults() +{ + // Set TLS options on the listening socket + mFD = SSL_ImportFD(nullptr, mFD); + if (NS_WARN_IF(!mFD)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSL_OptionSet(mFD, SSL_SECURITY, true); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true); + + // We don't currently notify the server API consumer of renegotiation events + // (to revalidate peer certs, etc.), so disable it for now. + SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER); + + SetSessionCache(true); + SetSessionTickets(true); + SetRequestClientCertificate(REQUEST_NEVER); + + return NS_OK; +} + +void +TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD, + const NetAddr& aClientAddr) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsresult rv; + + RefPtr 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 infoSupports = + NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info); + rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCondition = rv; + return; + } + + // Override the default peer certificate validation, so that server consumers + // can make their own choice after the handshake completes. + SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr); + // Once the TLS handshake has completed, the server consumer is notified and + // has access to various TLS state details. + // It's safe to pass info here because the socket transport holds it as + // |mSecInfo| which keeps it alive for the lifetime of the socket. + SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback, + info); + + // Notify the consumer of the new client so it can manage the streams. + // Security details aren't known yet. The security observer will be notified + // later when they are ready. + nsCOMPtr serverSocket = + do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this)); + mListener->OnSocketAccepted(serverSocket, trans); +} + +nsresult +TLSServerSocket::OnSocketListen() +{ + if (NS_WARN_IF(!mServerCert)) { + return NS_ERROR_NOT_INITIALIZED; + } + + UniqueCERTCertificate cert(mServerCert->GetCert()); + if (NS_WARN_IF(!cert)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr)); + if (NS_WARN_IF(!key)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSLKEAType certKEA = NSS_FindCertKEAType(cert.get()); + + nsresult rv = MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), + certKEA)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// static +SECStatus +TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig, + PRBool isServer) +{ + // Allow any client cert here, server consumer code can decide whether it's + // okay after being notified of the new client socket. + return SECSuccess; +} + +//----------------------------------------------------------------------------- +// TLSServerSocket::nsITLSServerSocket +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TLSServerSocket::GetServerCert(nsIX509Cert** aCert) +{ + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = mServerCert; + NS_IF_ADDREF(*aCert); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetServerCert(nsIX509Cert* aCert) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + mServerCert = aCert; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetSessionCache(bool aEnabled) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_NO_CACHE, !aEnabled); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetSessionTickets(bool aEnabled) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetRequestClientCertificate(uint32_t aMode) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER); + + switch (aMode) { + case REQUEST_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR); + break; + case REQUIRE_FIRST_HANDSHAKE: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE); + break; + case REQUIRE_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS); + break; + default: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER); + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetCipherSuites(uint16_t* aCipherSuites, uint32_t aLength) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + + for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) { + uint16_t cipher_id = SSL_ImplementedCiphers[i]; + if (SSL_CipherPrefSet(mFD, cipher_id, false) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + } + + for (uint32_t i = 0; i < aLength; ++i) { + if (SSL_CipherPrefSet(mFD, aCipherSuites[i], true) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion) +{ + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + + SSLVersionRange range = {aMinVersion, aMaxVersion}; + if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// TLSServerConnectionInfo +//----------------------------------------------------------------------------- + +namespace { + +class TLSServerSecurityObserverProxy final : public nsITLSServerSecurityObserver +{ + ~TLSServerSecurityObserverProxy() {} + +public: + explicit TLSServerSecurityObserverProxy(nsITLSServerSecurityObserver* aListener) + : mListener(new nsMainThreadPtrHolder(aListener)) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERSECURITYOBSERVER + + class OnHandshakeDoneRunnable : public Runnable + { + public: + OnHandshakeDoneRunnable(const nsMainThreadPtrHandle& aListener, + nsITLSServerSocket* aServer, + nsITLSClientStatus* aStatus) + : 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) + +TLSServerConnectionInfo::TLSServerConnectionInfo() + : mServerSocket(nullptr) + , mTransport(nullptr) + , mPeerCert(nullptr) + , mTlsVersionUsed(TLS_VERSION_UNKNOWN) + , mKeyLength(0) + , mMacLength(0) + , mLock("TLSServerConnectionInfo.mLock") + , mSecurityObserver(nullptr) +{ +} + +TLSServerConnectionInfo::~TLSServerConnectionInfo() +{ + if (!mSecurityObserver) { + return; + } + + RefPtr observer; + { + MutexAutoLock lock(mLock); + observer = mSecurityObserver.forget(); + } + + if (observer) { + NS_ReleaseOnMainThread(observer.forget()); + } +} + +NS_IMETHODIMP +TLSServerConnectionInfo::SetSecurityObserver(nsITLSServerSecurityObserver* aObserver) +{ + { + MutexAutoLock lock(mLock); + mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver); + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) +{ + if (NS_WARN_IF(!aSocket)) { + return NS_ERROR_INVALID_POINTER; + } + *aSocket = mServerSocket; + NS_IF_ADDREF(*aSocket); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) +{ + if (NS_WARN_IF(!aStatus)) { + return NS_ERROR_INVALID_POINTER; + } + *aStatus = this; + NS_IF_ADDREF(*aStatus); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) +{ + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = mPeerCert; + NS_IF_ADDREF(*aCert); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) +{ + if (NS_WARN_IF(!aTlsVersionUsed)) { + return NS_ERROR_INVALID_POINTER; + } + *aTlsVersionUsed = mTlsVersionUsed; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) +{ + aCipherName.Assign(mCipherName); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) +{ + if (NS_WARN_IF(!aKeyLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aKeyLength = mKeyLength; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) +{ + if (NS_WARN_IF(!aMacLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aMacLength = mMacLength; + return NS_OK; +} + +// static +void +TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) +{ + RefPtr 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; + rv = certDB->ConstructX509(reinterpret_cast(clientCert->derCert.data), + clientCert->derCert.len, + getter_AddRefs(clientCertPSM)); + if (NS_FAILED(rv)) { + return rv; + } + + mPeerCert = clientCertPSM; + } + + SSLChannelInfo channelInfo; + rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo))); + if (NS_FAILED(rv)) { + return rv; + } + mTlsVersionUsed = channelInfo.protocolVersion; + + SSLCipherSuiteInfo cipherInfo; + rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, + sizeof(cipherInfo))); + if (NS_FAILED(rv)) { + return rv; + } + mCipherName.Assign(cipherInfo.cipherSuiteName); + mKeyLength = cipherInfo.effectiveKeyBits; + mMacLength = cipherInfo.macBits; + + if (!mSecurityObserver) { + return NS_OK; + } + + // Notify consumer code that handshake is complete + nsCOMPtr observer; + { + MutexAutoLock lock(mLock); + 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 000000000..9fb57e0cc --- /dev/null +++ b/netwerk/base/TLSServerSocket.h @@ -0,0 +1,81 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_TLSServerSocket_h +#define mozilla_net_TLSServerSocket_h + +#include "nsAutoPtr.h" +#include "nsITLSServerSocket.h" +#include "nsServerSocket.h" +#include "nsString.h" +#include "mozilla/Mutex.h" +#include "seccomon.h" + +namespace mozilla { +namespace net { + +class TLSServerSocket final : public nsServerSocket + , public nsITLSServerSocket +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSISERVERSOCKET(nsServerSocket::) + NS_DECL_NSITLSSERVERSOCKET + + // Override methods from nsServerSocket + virtual void CreateClientTransport(PRFileDesc* clientFD, + const NetAddr& clientAddr) override; + virtual nsresult SetSocketDefaults() override; + virtual nsresult OnSocketListen() override; + + TLSServerSocket(); + +private: + virtual ~TLSServerSocket(); + + static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, PRBool isServer); + + nsCOMPtr mServerCert; +}; + +class TLSServerConnectionInfo : public nsITLSServerConnectionInfo + , public nsITLSClientStatus +{ + friend class TLSServerSocket; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERCONNECTIONINFO + NS_DECL_NSITLSCLIENTSTATUS + + TLSServerConnectionInfo(); + +private: + virtual ~TLSServerConnectionInfo(); + + static void HandshakeCallback(PRFileDesc* aFD, void* aArg); + nsresult HandshakeCallback(PRFileDesc* aFD); + + RefPtr mServerSocket; + // Weak ref to the transport, to avoid cycles since the transport holds a + // reference to the TLSServerConnectionInfo object. This is not handed out to + // anyone, and is only used in HandshakeCallback to close the transport in + // case of an error. After this, it's set to nullptr. + nsISocketTransport* mTransport; + nsCOMPtr mPeerCert; + int16_t mTlsVersionUsed; + nsCString mCipherName; + uint32_t mKeyLength; + uint32_t mMacLength; + // lock protects access to mSecurityObserver + mozilla::Mutex mLock; + nsCOMPtr mSecurityObserver; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_TLSServerSocket_h diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp new file mode 100644 index 000000000..d5b8a41df --- /dev/null +++ b/netwerk/base/ThrottleQueue.cpp @@ -0,0 +1,392 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ThrottleQueue.h" +#include "nsISeekableStream.h" +#include "nsIAsyncInputStream.h" +#include "nsStreamUtils.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- + +class ThrottleInputStream final + : public nsIAsyncInputStream + , public nsISeekableStream +{ +public: + + ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + void AllowInput(); + +private: + + ~ThrottleInputStream(); + + nsCOMPtr mStream; + RefPtr mQueue; + nsresult mClosedStatus; + + nsCOMPtr mCallback; + nsCOMPtr mEventTarget; +}; + +NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, nsISeekableStream) + +ThrottleInputStream::ThrottleInputStream(nsIInputStream *aStream, ThrottleQueue* aQueue) + : mStream(aStream) + , mQueue(aQueue) + , mClosedStatus(NS_OK) +{ + MOZ_ASSERT(aQueue != nullptr); +} + +ThrottleInputStream::~ThrottleInputStream() +{ + Close(); +} + +NS_IMETHODIMP +ThrottleInputStream::Close() +{ + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + if (mQueue) { + mQueue->DequeueStream(this); + mQueue = nullptr; + mClosedStatus = NS_BASE_STREAM_CLOSED; + } + return mStream->Close(); +} + +NS_IMETHODIMP +ThrottleInputStream::Available(uint64_t* aResult) +{ + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + return mStream->Available(aResult); +} + +NS_IMETHODIMP +ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->Read(aBuf, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + nsCOMPtr 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(mCallback, mEventTarget); + mCallback = nullptr; + mEventTarget = nullptr; + callbackEvent->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback) + +ThrottleQueue::ThrottleQueue() + : mMeanBytesPerSecond(0) + , mMaxBytesPerSecond(0) + , mBytesProcessed(0) + , mTimerArmed(false) +{ + nsresult rv; + nsCOMPtr sts; + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mTimer) + mTimer->SetTarget(sts); +} + +ThrottleQueue::~ThrottleQueue() +{ + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; +} + +NS_IMETHODIMP +ThrottleQueue::RecordRead(uint32_t aBytesRead) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + ThrottleEntry entry; + entry.mTime = TimeStamp::Now(); + entry.mBytesRead = aBytesRead; + mReadEvents.AppendElement(entry); + mBytesProcessed += aBytesRead; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + TimeStamp now = TimeStamp::Now(); + TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1); + size_t i; + + // Remove all stale events. + for (i = 0; i < mReadEvents.Length(); ++i) { + if (mReadEvents[i].mTime >= oneSecondAgo) { + break; + } + } + mReadEvents.RemoveElementsAt(0, i); + + uint32_t totalBytes = 0; + for (i = 0; i < mReadEvents.Length(); ++i) { + totalBytes += mReadEvents[i].mBytesRead; + } + + uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond; + double prob = static_cast(rand()) / RAND_MAX; + uint32_t thisSliceBytes = mMeanBytesPerSecond - spread + + static_cast(2 * spread * prob); + + if (totalBytes >= thisSliceBytes) { + *aAvailable = 0; + } else { + *aAvailable = thisSliceBytes; + } + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) +{ + // Can be called on any thread. + if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || aMaxBytesPerSecond < aMeanBytesPerSecond) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mMeanBytesPerSecond = aMeanBytesPerSecond; + mMaxBytesPerSecond = aMaxBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::BytesProcessed(uint64_t* aResult) +{ + *aResult = mBytesProcessed; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::WrapStream(nsIInputStream* aInputStream, nsIAsyncInputStream** aResult) +{ + nsCOMPtr result = new ThrottleInputStream(aInputStream, this); + result.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Notify(nsITimer* aTimer) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + // A notified reader may need to push itself back on the queue. + // Swap out the list of readers so that this works properly. + nsTArray> events; + events.SwapElements(mAsyncEvents); + + // Optimistically notify all the waiting readers, and then let them + // requeue if there isn't enough bandwidth. + for (size_t i = 0; i < events.Length(); ++i) { + events[i]->AllowInput(); + } + + mTimerArmed = false; + return NS_OK; +} + +void +ThrottleQueue::QueueStream(ThrottleInputStream* aStream) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (mAsyncEvents.IndexOf(aStream) == mAsyncEvents.NoIndex) { + mAsyncEvents.AppendElement(aStream); + + if (!mTimerArmed) { + uint32_t ms = 1000; + if (mReadEvents.Length() > 0) { + TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1); + TimeStamp now = TimeStamp::Now(); + + if (t > now) { + ms = static_cast((t - now).ToMilliseconds()); + } else { + ms = 1; + } + } + + if (NS_SUCCEEDED(mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) { + mTimerArmed = true; + } + } + } +} + +void +ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + mAsyncEvents.RemoveElement(aStream); +} + +} +} diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h new file mode 100644 index 000000000..5e16c8ef6 --- /dev/null +++ b/netwerk/base/ThrottleQueue.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ThrottleQueue_h +#define mozilla_net_ThrottleQueue_h + +#include "mozilla/TimeStamp.h" +#include "nsIThrottledInputChannel.h" +#include "nsITimer.h" + +namespace mozilla { +namespace net { + +class ThrottleInputStream; + +/** + * An implementation of nsIInputChannelThrottleQueue that can be used + * to throttle uploads. This class is not thread-safe. + * Initialization and calls to WrapStream may be done on any thread; + * but otherwise, after creation, it can only be used on the socket + * thread. It currently throttles with a one second granularity, so + * may be a bit choppy. + */ + +class ThrottleQueue final + : public nsIInputChannelThrottleQueue + , public nsITimerCallback +{ +public: + + ThrottleQueue(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE + NS_DECL_NSITIMERCALLBACK + + void QueueStream(ThrottleInputStream* aStream); + void DequeueStream(ThrottleInputStream* aStream); + +private: + + ~ThrottleQueue(); + + struct ThrottleEntry { + TimeStamp mTime; + uint32_t mBytesRead; + }; + + nsTArray mReadEvents; + uint32_t mMeanBytesPerSecond; + uint32_t mMaxBytesPerSecond; + uint64_t mBytesProcessed; + + nsTArray> mAsyncEvents; + nsCOMPtr mTimer; + bool mTimerArmed; +}; + +} +} + +#endif // mozilla_net_ThrottleQueue_h diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp new file mode 100644 index 000000000..555fdbbe5 --- /dev/null +++ b/netwerk/base/Tickler.cpp @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Tickler.h" + +#ifdef MOZ_USE_WIFI_TICKLER +#include "nsComponentManagerUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "prnetdb.h" + +#include "mozilla/jni/Utils.h" +#include "GeneratedJNIWrappers.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler) + +Tickler::Tickler() + : mLock("Tickler::mLock") + , mActive(false) + , mCanceled(false) + , mEnabled(false) + , mDelay(16) + , mDuration(TimeDuration::FromMilliseconds(400)) + , mFD(nullptr) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +Tickler::~Tickler() +{ + // non main thread uses of the tickler should hold weak + // references to it if they must hold a reference at all + MOZ_ASSERT(NS_IsMainThread()); + + if (mThread) { + mThread->AsyncShutdown(); + mThread = nullptr; + } + + if (mTimer) + mTimer->Cancel(); + if (mFD) + PR_Close(mFD); +} + +nsresult +Tickler::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mTimer); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(!mThread); + MOZ_ASSERT(!mFD); + + if (jni::IsAvailable()) { + java::GeckoAppShell::EnableNetworkNotifications(); + } + + mFD = PR_OpenUDPSocket(PR_AF_INET); + if (!mFD) + return NS_ERROR_FAILURE; + + // make sure new socket has a ttl of 1 + // failure is not fatal. + PRSocketOptionData opt; + opt.option = PR_SockOpt_IpTimeToLive; + opt.value.ip_ttl = 1; + PR_SetSocketOption(mFD, &opt); + + nsresult rv = NS_NewNamedThread("wifi tickler", + getter_AddRefs(mThread)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr tmpTimer(do_CreateInstance(NS_TIMER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + rv = tmpTimer->SetTarget(mThread); + if (NS_FAILED(rv)) + return rv; + + mTimer.swap(tmpTimer); + + mAddr.inet.family = PR_AF_INET; + mAddr.inet.port = PR_htons (4886); + mAddr.inet.ip = 0; + + return NS_OK; +} + +void Tickler::Tickle() +{ + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread); + mLastTickle = TimeStamp::Now(); + if (!mActive) + MaybeStartTickler(); +} + +void Tickler::PostCheckTickler() +{ + mLock.AssertCurrentThreadOwns(); + mThread->Dispatch(NewRunnableMethod(this, &Tickler::CheckTickler), + NS_DISPATCH_NORMAL); + return; +} + +void Tickler::MaybeStartTicklerUnlocked() +{ + MutexAutoLock lock(mLock); + MaybeStartTickler(); +} + +void Tickler::MaybeStartTickler() +{ + mLock.AssertCurrentThreadOwns(); + if (!NS_IsMainThread()) { + NS_DispatchToMainThread( + NewRunnableMethod(this, &Tickler::MaybeStartTicklerUnlocked)); + return; + } + + if (!mPrefs) + mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (mPrefs) { + int32_t val; + bool boolVal; + + if (NS_SUCCEEDED(mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal))) + mEnabled = boolVal; + + if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) { + if (val < 1) + val = 1; + if (val > 100000) + val = 100000; + mDuration = TimeDuration::FromMilliseconds(val); + } + + if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) { + if (val < 1) + val = 1; + if (val > 1000) + val = 1000; + mDelay = static_cast(val); + } + } + + PostCheckTickler(); +} + +void Tickler::CheckTickler() +{ + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + + bool shouldRun = (!mCanceled) && + ((TimeStamp::Now() - mLastTickle) <= mDuration); + + if ((shouldRun && mActive) || (!shouldRun && !mActive)) + return; // no change in state + + if (mActive) + StopTickler(); + else + StartTickler(); +} + +void Tickler::Cancel() +{ + MutexAutoLock lock(mLock); + MOZ_ASSERT(NS_IsMainThread()); + mCanceled = true; + if (mThread) + PostCheckTickler(); +} + +void Tickler::StopTickler() +{ + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(mTimer); + MOZ_ASSERT(mActive); + + mTimer->Cancel(); + mActive = false; +} + +class TicklerTimer final : public nsITimerCallback +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + TicklerTimer(Tickler *aTickler) + { + mTickler = do_GetWeakReference(aTickler); + } + +private: + ~TicklerTimer() {} + + nsWeakPtr mTickler; +}; + +void Tickler::StartTickler() +{ + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(mTimer); + + if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this), + mEnabled ? mDelay : 1000, + nsITimer::TYPE_REPEATING_SLACK))) + mActive = true; +} + +// argument should be in network byte order +void Tickler::SetIPV4Address(uint32_t address) +{ + mAddr.inet.ip = address; +} + +// argument should be in network byte order +void Tickler::SetIPV4Port(uint16_t port) +{ + mAddr.inet.port = port; +} + +NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback) + +NS_IMETHODIMP TicklerTimer::Notify(nsITimer *timer) +{ + RefPtr tickler = do_QueryReferent(mTickler); + if (!tickler) + return NS_ERROR_FAILURE; + MutexAutoLock lock(tickler->mLock); + + if (!tickler->mFD) { + tickler->StopTickler(); + return NS_ERROR_FAILURE; + } + + if (tickler->mCanceled || + ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) { + tickler->StopTickler(); + return NS_OK; + } + + if (!tickler->mEnabled) + return NS_OK; + + PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0); + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla + +#else // not defined MOZ_USE_WIFI_TICKLER + +namespace mozilla { +namespace net { +NS_IMPL_ISUPPORTS0(Tickler) +} // namespace net +} // namespace mozilla + +#endif // defined MOZ_USE_WIFI_TICKLER + diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h new file mode 100644 index 000000000..573fe6e76 --- /dev/null +++ b/netwerk/base/Tickler.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Tickler_h +#define mozilla_net_Tickler_h + +// The tickler sends a regular 0 byte UDP heartbeat out to a +// particular address for a short time after it has been touched. This +// is used on some mobile wifi chipsets to mitigate Power Save Polling +// (PSP) Mode when we are anticipating a response packet +// soon. Typically PSP adds 100ms of latency to a read event because +// the packet delivery is not triggered until the 802.11 beacon is +// delivered to the host (100ms is the standard Access Point +// configuration for the beacon interval.) Requesting a frequent +// transmission and getting a CTS frame from the AP at least that +// frequently allows for low latency receives when we have reason to +// expect them (e.g a SYN-ACK). +// +// The tickler is used to allow RTT based phases of web transport to +// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL +// handshake, HTTP headers, and the TCP slow start phase. The +// transaction is given up to 400 miliseconds by default to get +// through those phases before the tickler is disabled. +// +// The tickler only applies to wifi on mobile right now. Hopefully it +// can also be restricted to particular handset models in the future. + +#if defined(ANDROID) && !defined(MOZ_B2G) +#define MOZ_USE_WIFI_TICKLER +#endif + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include + +#ifdef MOZ_USE_WIFI_TICKLER +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsAutoPtr.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nsITimer.h" +#include "nsWeakReference.h" +#include "prio.h" + +class nsIPrefBranch; +#endif + +namespace mozilla { +namespace net { + +#ifdef MOZ_USE_WIFI_TICKLER + +// 8f769ed6-207c-4af9-9f7e-9e832da3754e +#define NS_TICKLER_IID \ +{ 0x8f769ed6, 0x207c, 0x4af9, \ + { 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e } } + +class Tickler final : public nsSupportsWeakReference +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID) + + // These methods are main thread only + Tickler(); + void Cancel(); + nsresult Init(); + void SetIPV4Address(uint32_t address); + void SetIPV4Port(uint16_t port); + + // Tickle the tickler to (re-)start the activity. + // May call from any thread + void Tickle(); + +private: + ~Tickler(); + + friend class TicklerTimer; + Mutex mLock; + nsCOMPtr 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() { } +public: + NS_DECL_THREADSAFE_ISUPPORTS + + Tickler() { } + nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; } + void Cancel() { } + void SetIPV4Address(uint32_t) { }; + void SetIPV4Port(uint16_t) { } + void Tickle() { } +}; + +#endif // defined MOZ_USE_WIFI_TICKLER + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Tickler_h diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build new file mode 100644 index 000000000..3b731db10 --- /dev/null +++ b/netwerk/base/moz.build @@ -0,0 +1,316 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'mozIThirdPartyUtil.idl', + 'nsIApplicationCache.idl', + 'nsIApplicationCacheChannel.idl', + 'nsIApplicationCacheContainer.idl', + 'nsIApplicationCacheService.idl', + 'nsIArrayBufferInputStream.idl', + 'nsIAsyncStreamCopier.idl', + 'nsIAsyncStreamCopier2.idl', + 'nsIAsyncVerifyRedirectCallback.idl', + 'nsIAuthInformation.idl', + 'nsIAuthModule.idl', + 'nsIAuthPrompt.idl', + 'nsIAuthPrompt2.idl', + 'nsIAuthPromptAdapterFactory.idl', + 'nsIAuthPromptCallback.idl', + 'nsIAuthPromptProvider.idl', + 'nsIBackgroundFileSaver.idl', + 'nsIBufferedStreams.idl', + 'nsIByteRangeRequest.idl', + 'nsICacheInfoChannel.idl', + 'nsICachingChannel.idl', + 'nsICancelable.idl', + 'nsICaptivePortalService.idl', + 'nsIChannel.idl', + 'nsIChannelEventSink.idl', + 'nsIChannelWithDivertableParentListener.idl', + 'nsIChildChannel.idl', + 'nsIClassOfService.idl', + 'nsIContentSniffer.idl', + 'nsICryptoFIPSInfo.idl', + 'nsICryptoHash.idl', + 'nsICryptoHMAC.idl', + 'nsIDashboard.idl', + 'nsIDashboardEventNotifier.idl', + 'nsIDeprecationWarner.idl', + 'nsIDivertableChannel.idl', + 'nsIDownloader.idl', + 'nsIEncodedChannel.idl', + 'nsIExternalProtocolHandler.idl', + 'nsIFileStreams.idl', + 'nsIFileURL.idl', + 'nsIForcePendingChannel.idl', + 'nsIFormPOSTActionChannel.idl', + 'nsIHttpAuthenticatorCallback.idl', + 'nsIHttpPushListener.idl', + 'nsIIncrementalDownload.idl', + 'nsIIncrementalStreamLoader.idl', + 'nsIInputStreamChannel.idl', + 'nsIInputStreamPump.idl', + 'nsIIOService.idl', + 'nsIIOService2.idl', + 'nsILoadContextInfo.idl', + 'nsILoadGroup.idl', + 'nsILoadGroupChild.idl', + 'nsILoadInfo.idl', + 'nsIMIMEInputStream.idl', + 'nsIMultiPartChannel.idl', + 'nsINestedURI.idl', + 'nsINetAddr.idl', + 'nsINetUtil.idl', + 'nsINetworkInfoService.idl', + 'nsINetworkInterceptController.idl', + 'nsINetworkLinkService.idl', + 'nsINetworkPredictor.idl', + 'nsINetworkPredictorVerifier.idl', + 'nsINetworkProperties.idl', + 'nsINSSErrorsService.idl', + 'nsINullChannel.idl', + 'nsIParentChannel.idl', + 'nsIParentRedirectingChannel.idl', + 'nsIPermission.idl', + 'nsIPermissionManager.idl', + 'nsIPrivateBrowsingChannel.idl', + 'nsIProgressEventSink.idl', + 'nsIPrompt.idl', + 'nsIProtocolHandler.idl', + 'nsIProtocolProxyCallback.idl', + 'nsIProtocolProxyFilter.idl', + 'nsIProtocolProxyService.idl', + 'nsIProtocolProxyService2.idl', + 'nsIProxiedChannel.idl', + 'nsIProxiedProtocolHandler.idl', + 'nsIProxyInfo.idl', + 'nsIRandomGenerator.idl', + 'nsIRedirectChannelRegistrar.idl', + 'nsIRedirectResultListener.idl', + 'nsIRequest.idl', + 'nsIRequestContext.idl', + 'nsIRequestObserver.idl', + 'nsIRequestObserverProxy.idl', + 'nsIResumableChannel.idl', + 'nsISecCheckWrapChannel.idl', + 'nsISecureBrowserUI.idl', + 'nsISecurityEventSink.idl', + 'nsISecurityInfoProvider.idl', + 'nsISensitiveInfoHiddenURI.idl', + 'nsISerializationHelper.idl', + 'nsIServerSocket.idl', + 'nsISimpleStreamListener.idl', + 'nsISocketFilter.idl', + 'nsISocketTransport.idl', + 'nsISocketTransportService.idl', + 'nsISpeculativeConnect.idl', + 'nsIStandardURL.idl', + 'nsIStreamingProtocolController.idl', + 'nsIStreamingProtocolService.idl', + 'nsIStreamListener.idl', + 'nsIStreamListenerTee.idl', + 'nsIStreamLoader.idl', + 'nsIStreamTransportService.idl', + 'nsISyncStreamListener.idl', + 'nsISystemProxySettings.idl', + 'nsIThreadRetargetableRequest.idl', + 'nsIThreadRetargetableStreamListener.idl', + 'nsIThrottledInputChannel.idl', + 'nsITimedChannel.idl', + 'nsITLSServerSocket.idl', + 'nsITraceableChannel.idl', + 'nsITransport.idl', + 'nsIUDPSocket.idl', + 'nsIUnicharStreamLoader.idl', + 'nsIUploadChannel.idl', + 'nsIUploadChannel2.idl', + 'nsIURI.idl', + 'nsIURIClassifier.idl', + 'nsIURIWithBlobImpl.idl', + 'nsIURIWithPrincipal.idl', + 'nsIURIWithQuery.idl', + 'nsIURL.idl', + 'nsIURLParser.idl', + 'nsPILoadGroupInternal.idl', + 'nsPISocketTransportService.idl', +] + +if CONFIG['MOZ_TOOLKIT_SEARCH']: + XPIDL_SOURCES += [ + 'nsIBrowserSearchService.idl', + ] + +XPIDL_MODULE = 'necko' + +EXPORTS += [ + 'netCore.h', + 'nsASocketHandler.h', + 'nsAsyncRedirectVerifyHelper.h', + 'nsFileStreams.h', + 'nsInputStreamPump.h', + 'nsMIMEInputStream.h', + 'nsNetUtil.h', + 'nsNetUtilInlines.h', + 'nsReadLine.h', + 'nsSerializationHelper.h', + 'nsSimpleNestedURI.h', + 'nsSimpleURI.h', + 'nsStreamListenerWrapper.h', + 'nsTemporaryFileInputStream.h', + 'nsURIHashKey.h', + 'nsURLHelper.h', + 'nsURLParsers.h', +] + +EXPORTS.mozilla += [ + 'LoadContextInfo.h', + 'LoadInfo.h', + 'LoadTainting.h', +] + +EXPORTS.mozilla.net += [ + 'CaptivePortalService.h', + 'ChannelDiverterChild.h', + 'ChannelDiverterParent.h', + 'Dashboard.h', + 'DashboardTypes.h', + 'MemoryDownloader.h', + 'Predictor.h', + 'ReferrerPolicy.h', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + EXPORTS += [ + 'NetStatistics.h', + ] + +UNIFIED_SOURCES += [ + 'ArrayBufferInputStream.cpp', + 'BackgroundFileSaver.cpp', + 'CaptivePortalService.cpp', + 'ChannelDiverterChild.cpp', + 'ChannelDiverterParent.cpp', + 'Dashboard.cpp', + 'EventTokenBucket.cpp', + 'LoadContextInfo.cpp', + 'LoadInfo.cpp', + 'MemoryDownloader.cpp', + 'NetworkActivityMonitor.cpp', + 'nsAsyncRedirectVerifyHelper.cpp', + 'nsAsyncStreamCopier.cpp', + 'nsAuthInformationHolder.cpp', + 'nsBase64Encoder.cpp', + 'nsBaseChannel.cpp', + 'nsBaseContentStream.cpp', + 'nsBufferedStreams.cpp', + 'nsChannelClassifier.cpp', + 'nsDirectoryIndexStream.cpp', + 'nsDNSPrefetch.cpp', + 'nsDownloader.cpp', + 'nsFileStreams.cpp', + 'nsIncrementalDownload.cpp', + 'nsIncrementalStreamLoader.cpp', + 'nsInputStreamChannel.cpp', + 'nsInputStreamPump.cpp', + 'nsIOService.cpp', + 'nsLoadGroup.cpp', + 'nsMediaFragmentURIParser.cpp', + 'nsMIMEInputStream.cpp', + 'nsNetAddr.cpp', + 'nsNetUtil.cpp', + 'nsPACMan.cpp', + 'nsPreloadedStream.cpp', + 'nsProtocolProxyService.cpp', + 'nsProxyInfo.cpp', + 'nsRequestObserverProxy.cpp', + 'nsSecCheckWrapChannel.cpp', + 'nsSerializationHelper.cpp', + 'nsServerSocket.cpp', + 'nsSimpleNestedURI.cpp', + 'nsSimpleStreamListener.cpp', + 'nsSimpleURI.cpp', + 'nsSocketTransport2.cpp', + 'nsSocketTransportService2.cpp', + 'nsStandardURL.cpp', + 'nsStreamListenerTee.cpp', + 'nsStreamListenerWrapper.cpp', + 'nsStreamLoader.cpp', + 'nsStreamTransportService.cpp', + 'nsSyncStreamListener.cpp', + 'nsTemporaryFileInputStream.cpp', + 'nsTransportUtils.cpp', + 'nsUDPSocket.cpp', + 'nsUnicharStreamLoader.cpp', + 'nsURLHelper.cpp', + 'nsURLParsers.cpp', + 'PollableEvent.cpp', + 'Predictor.cpp', + 'ProxyAutoConfig.cpp', + 'RedirectChannelRegistrar.cpp', + 'RequestContextService.cpp', + 'SimpleBuffer.cpp', + 'StreamingProtocolService.cpp', + 'ThrottleQueue.cpp', + 'Tickler.cpp', + 'TLSServerSocket.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'nsURLHelperWin.cpp', + 'ShutdownLayer.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsURLHelperOSX.cpp', + ] +else: + SOURCES += [ + 'nsURLHelperUnix.cpp', + ] + +# nsINetworkInfoService support. +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'NetworkInfoServiceWindows.cpp', + 'nsNetworkInfoService.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'NetworkInfoServiceCocoa.cpp', + 'nsNetworkInfoService.cpp', + ] +elif CONFIG['OS_ARCH'] == 'Linux': + SOURCES += [ + 'NetworkInfoServiceLinux.cpp', + 'nsNetworkInfoService.cpp', + ] + +EXTRA_JS_MODULES += [ + 'NetUtil.jsm', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', + '/netwerk/protocol/http', + '/netwerk/socket', + '/security/pkix/include' +] + +if 'rtsp' in CONFIG['NECKO_PROTOCOLS']: + LOCAL_INCLUDES += [ + '/netwerk/protocol/rtsp/controller', + '/netwerk/protocol/rtsp/rtsp', + ] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl new file mode 100644 index 000000000..2eea9550a --- /dev/null +++ b/netwerk/base/mozIThirdPartyUtil.idl @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface mozIDOMWindowProxy; +interface nsIChannel; + +/** + * Utility functions for determining whether a given URI, channel, or window + * hierarchy is third party with respect to a known URI. + */ +[scriptable, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)] +interface mozIThirdPartyUtil : nsISupports +{ + /** + * isThirdPartyURI + * + * Determine whether two URIs are third party with respect to each other. + * This is determined by computing the base domain for both URIs. If they can + * be determined, and the base domains match, the request is defined as first + * party. If it cannot be determined because one or both URIs do not have a + * base domain (for instance, in the case of IP addresses, host aliases such + * as 'localhost', or a file:// URI), an exact string comparison on host is + * performed. + * + * For example, the URI "http://mail.google.com/" is not third party with + * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and + * "http://192.168.1.1/" are. + * + * @return true if aFirstURI is third party with respect to aSecondURI. + * + * @throws if either URI is null, has a malformed host, or has an empty host + * and is not a file:// URI. + */ + boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI); + + /** + * isThirdPartyWindow + * + * Determine whether the given window hierarchy is third party. This is done + * as follows: + * + * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the + * 'bottom URI'. + * 2) If 'aURI' is provided, determine if it is third party with respect to + * the bottom URI. If so, return. + * 3) Find the same-type parent window, if there is one, and its URI. + * Determine whether it is third party with respect to the bottom URI. If + * so, return. + * + * Therefore, each level in the window hierarchy is tested. (This means that + * nested iframes with different base domains, even though the bottommost and + * topmost URIs might be equal, will be considered third party.) + * + * @param aWindow + * The bottommost window in the hierarchy. + * @param aURI + * A URI to test against. If null, the URI of the principal + * associated with 'aWindow' will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if 'aURI' is third party with respect to any of the URIs + * associated with aWindow and its same-type parents. + * + * @throws if aWindow is null; the same-type parent of any window in the + * hierarchy cannot be determined; or the URI associated with any + * window in the hierarchy is null, has a malformed host, or has an + * empty host and is not a file:// URI. + * + * @see isThirdPartyURI + */ + boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI); + + /** + * isThirdPartyChannel + * + * Determine whether the given channel and its content window hierarchy is + * third party. This is done as follows: + * + * 1) If 'aChannel' is an nsIHttpChannel and has the + * 'forceAllowThirdPartyCookie' property set, then: + * a) If 'aURI' is null, return false. + * b) Otherwise, find the URI of the channel, determine whether it is + * foreign with respect to 'aURI', and return. + * 2) Find the URI of the channel and determine whether it is third party with + * respect to the URI of the channel. If so, return. + * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it + * exists, from the channel's notification callbacks. Then: + * a) If the parent is the same as the bottommost window, and the channel + * has the LOAD_DOCUMENT_URI flag set, return false. This represents the + * case where a toplevel load is occurring and the window's URI has not + * yet been updated. (We have already checked that 'aURI' is not foreign + * with respect to the channel URI.) + * b) Otherwise, return the result of isThirdPartyWindow with arguments + * of the channel's bottommost window and the channel URI, respectively. + * + * Therefore, both the channel's URI and each level in the window hierarchy + * associated with the channel is tested. + * + * @param aChannel + * The channel associated with the load. + * @param aURI + * A URI to test against. If null, the URI of the channel will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if aURI is third party with respect to the channel URI or any + * of the URIs associated with the same-type window hierarchy of the + * channel. + * + * @throws if 'aChannel' is null; the channel has no notification callbacks or + * an associated window; or isThirdPartyWindow throws. + * + * @see isThirdPartyWindow + */ + boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI); + + /** + * getBaseDomain + * + * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be + * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing + * dot may be present. If aHostURI is an IP address, an alias such as + * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will + * be the exact host. The result of this function should only be used in exact + * string comparisons, since substring comparisons will not be valid for the + * special cases elided above. + * + * @param aHostURI + * The URI to analyze. + * + * @return the base domain. + */ + AUTF8String getBaseDomain(in nsIURI aHostURI); + + /** + * getURIFromWindow + * + * Returns the URI associated with the script object principal for the + * window. + */ + nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getTopWindowForChannel + * + * Returns the top-level window associated with the given channel. + */ + mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel); +}; + +%{ C++ +/** + * The mozIThirdPartyUtil implementation is an XPCOM service registered + * under the ContractID: + */ +#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1" +%} + diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h new file mode 100644 index 000000000..7a0738cf9 --- /dev/null +++ b/netwerk/base/netCore.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __netCore_h__ +#define __netCore_h__ + +#include "nsError.h" + +// Where most necko status messages come from: +#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties" + +#endif // __netCore_h__ diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h new file mode 100644 index 000000000..c15daecd8 --- /dev/null +++ b/netwerk/base/nsASocketHandler.h @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsASocketHandler_h__ +#define nsASocketHandler_h__ + +// socket handler used by nsISocketTransportService. +// methods are only called on the socket thread. + +class nsASocketHandler : public nsISupports +{ +public: + nsASocketHandler() + : mCondition(NS_OK) + , mPollFlags(0) + , mPollTimeout(UINT16_MAX) + , mIsPrivate(false) + {} + + // + // this condition variable will be checked to determine if the socket + // handler should be detached. it must only be accessed on the socket + // thread. + // + nsresult mCondition; + + // + // these flags can only be modified on the socket transport thread. + // the socket transport service will check these flags before calling + // PR_Poll. + // + uint16_t mPollFlags; + + // + // this value specifies the maximum amount of time in seconds that may be + // spent waiting for activity on this socket. if this timeout is reached, + // then OnSocketReady will be called with outFlags = -1. + // + // the default value for this member is UINT16_MAX, which disables the + // timeout error checking. (i.e., a timeout value of UINT16_MAX is + // never reached.) + // + uint16_t mPollTimeout; + + bool mIsPrivate; + + // + // called to service a socket + // + // params: + // socketRef - socket identifier + // fd - socket file descriptor + // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns + // or -1 if a timeout occurred + // + virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags) = 0; + + // + // called when a socket is no longer under the control of the socket + // transport service. the socket handler may close the socket at this + // point. after this call returns, the handler will no longer be owned + // by the socket transport service. + // + virtual void OnSocketDetached(PRFileDesc *fd) = 0; + + // + // called to determine if the socket is for a local peer. + // when used for server sockets, indicates if it only accepts local + // connections. + // + virtual void IsLocal(bool *aIsLocal) = 0; + + // + // called to determine if this socket should not be terminated when Gecko + // is turned offline. This is mostly useful for the debugging server + // socket. + // + virtual void KeepWhenOffline(bool *aKeepWhenOffline) + { + *aKeepWhenOffline = false; + } + + // + // called when global pref for keepalive has changed. + // + virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) { } + + // + // returns the number of bytes sent/transmitted over the socket + // + virtual uint64_t ByteCountSent() = 0; + virtual uint64_t ByteCountReceived() = 0; +}; + +#endif // !nsASocketHandler_h__ diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp new file mode 100644 index 000000000..3b19b93c7 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" + +#include "nsIOService.h" +#include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadInfo.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gRedirectLog("nsRedirect"); +#undef LOG +#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, + nsIAsyncVerifyRedirectCallback, + nsIRunnable) + +class nsAsyncVerifyRedirectCallbackEvent : public Runnable { +public: + nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb, + nsresult result) + : mCallback(cb), mResult(result) { + } + + NS_IMETHOD Run() override + { + LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() " + "callback to %p with result %x", + mCallback.get(), mResult)); + (void) mCallback->OnRedirectVerifyCallback(mResult); + return NS_OK; + } +private: + nsCOMPtr mCallback; + nsresult mResult; +}; + +nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper() + : mFlags(0), + mWaitingForRedirectCallback(false), + mCallbackInitiated(false), + mExpectedCallbacks(0), + mResult(NS_OK) +{ +} + +nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() +{ + NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, + "Did not receive all required callbacks!"); +} + +nsresult +nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan, + uint32_t flags, bool synchronize) +{ + LOG(("nsAsyncRedirectVerifyHelper::Init() " + "oldChan=%p newChan=%p", oldChan, newChan)); + mOldChan = oldChan; + mNewChan = newChan; + mFlags = flags; + mCallbackThread = do_GetCurrentThread(); + + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr loadInfo = oldChan->GetLoadInfo(); + if (loadInfo && loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + + if (synchronize) + mWaitingForRedirectCallback = true; + + nsresult rv; + rv = NS_DispatchToMainThread(this); + NS_ENSURE_SUCCESS(rv, rv); + + if (synchronize) { + nsIThread *thread = NS_GetCurrentThread(); + while (mWaitingForRedirectCallback) { + if (!NS_ProcessNextEvent(thread)) { + return NS_ERROR_UNEXPECTED; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) +{ + LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " + "result=%x expectedCBs=%u mResult=%x", + result, mExpectedCallbacks, mResult)); + + MOZ_DIAGNOSTIC_ASSERT(mExpectedCallbacks > 0, + "OnRedirectVerifyCallback called more times than expected"); + if (mExpectedCallbacks <= 0) { + return NS_ERROR_UNEXPECTED; + } + + --mExpectedCallbacks; + + // If response indicates failure we may call back immediately + if (NS_FAILED(result)) { + // We chose to store the first failure-value (as opposed to the last) + if (NS_SUCCEEDED(mResult)) + mResult = result; + + // If InitCallback() has been called, just invoke the callback and + // return. Otherwise it will be invoked from InitCallback() + if (mCallbackInitiated) { + ExplicitCallback(mResult); + return NS_OK; + } + } + + // If the expected-counter is in balance and InitCallback() was called, all + // sinks have agreed that the redirect is ok and we can invoke our callback + if (mCallbackInitiated && mExpectedCallbacks == 0) { + ExplicitCallback(mResult); + } + + return NS_OK; +} + +nsresult +nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink, + nsIChannel *oldChannel, + nsIChannel *newChannel, + uint32_t flags) +{ + LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " + "sink=%p expectedCBs=%u mResult=%x", + sink, mExpectedCallbacks, mResult)); + + ++mExpectedCallbacks; + + if (IsOldChannelCanceled()) { + LOG((" old channel has been canceled, cancel the redirect by " + "emulating OnRedirectVerifyCallback...")); + (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED); + return NS_BINDING_ABORTED; + } + + nsresult rv = + sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); + + LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks)); + + // If the sink returns failure from this call the redirect is vetoed. We + // emulate a callback from the sink in this case in order to perform all + // the necessary logic. + if (NS_FAILED(rv)) { + LOG((" emulating OnRedirectVerifyCallback...")); + (void) OnRedirectVerifyCallback(rv); + } + + return rv; // Return the actual status since our caller may need it +} + +void +nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) +{ + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x", + result, mExpectedCallbacks, mCallbackInitiated, mResult)); + + nsCOMPtr + callback(do_QueryInterface(mOldChan)); + + if (!callback || !mCallbackThread) { + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get())); + return; + } + + mCallbackInitiated = false; // reset to ensure only one callback + mWaitingForRedirectCallback = false; + + // Now, dispatch the callback on the event-target which called Init() + nsCOMPtr event = + new nsAsyncVerifyRedirectCallbackEvent(callback, result); + if (!event) { + NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed creating callback event!"); + return; + } + nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed dispatching callback event!"); + } else { + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "dispatched callback event=%p", event.get())); + } + +} + +void +nsAsyncRedirectVerifyHelper::InitCallback() +{ + LOG(("nsAsyncRedirectVerifyHelper::InitCallback() " + "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult)); + + mCallbackInitiated = true; + + // Invoke the callback if we are done + if (mExpectedCallbacks == 0) + ExplicitCallback(mResult); +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::Run() +{ + /* If the channel got canceled after it fired AsyncOnChannelRedirect + * and before we got here, mostly because docloader load has been canceled, + * we must completely ignore this notification and prevent any further + * notification. + */ + if (IsOldChannelCanceled()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + + // First, the global observer + NS_ASSERTION(gIOService, "Must have an IO service at this point"); + LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); + nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, + mFlags, this); + if (NS_FAILED(rv)) { + ExplicitCallback(rv); + return NS_OK; + } + + // Now, the per-channel observers + nsCOMPtr sink; + NS_QueryNotificationCallbacks(mOldChan, sink); + if (sink) { + LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); + rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); + } + + // All invocations to AsyncOnChannelRedirect has been done - call + // InitCallback() to flag this + InitCallback(); + return NS_OK; +} + +bool +nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() +{ + bool canceled; + nsCOMPtr oldChannelInternal = + do_QueryInterface(mOldChan); + if (oldChannelInternal) { + oldChannelInternal->GetCanceled(&canceled); + if (canceled) { + return true; + } + } else if (mOldChan) { + // For non-HTTP channels check on the status, failure + // indicates the channel has probably been canceled. + nsresult status = NS_ERROR_FAILURE; + mOldChan->GetStatus(&status); + if (NS_FAILED(status)) { + return true; + } + } + + return false; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h new file mode 100644 index 000000000..f67785498 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncRedirectVerifyHelper_h +#define nsAsyncRedirectVerifyHelper_h + +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +/** + * This class simplifies call of OnChannelRedirect of IOService and + * the sink bound with the channel being redirected while the result of + * redirect decision is returned through the callback. + */ +class nsAsyncRedirectVerifyHelper final : public nsIRunnable, + public nsIAsyncVerifyRedirectCallback +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + +public: + nsAsyncRedirectVerifyHelper(); + + /* + * Calls AsyncOnChannelRedirect() on the given sink with the given + * channels and flags. Keeps track of number of async callbacks to expect. + */ + nsresult DelegateOnChannelRedirect(nsIChannelEventSink *sink, + nsIChannel *oldChannel, + nsIChannel *newChannel, + uint32_t flags); + + /** + * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel + * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect + * decision is passed through this interface back to the oldChannel. + * + * @param oldChan + * channel being redirected, MUST implement + * nsIAsyncVerifyRedirectCallback + * @param newChan + * target of the redirect channel + * @param flags + * redirect flags + * @param synchronize + * set to TRUE if you want the Init method wait synchronously for + * all redirect callbacks + */ + nsresult Init(nsIChannel* oldChan, + nsIChannel* newChan, + uint32_t flags, + bool synchronize = false); + +protected: + nsCOMPtr mOldChan; + nsCOMPtr mNewChan; + uint32_t mFlags; + bool mWaitingForRedirectCallback; + nsCOMPtr mCallbackThread; + bool mCallbackInitiated; + int32_t mExpectedCallbacks; + nsresult mResult; // value passed to callback + + void InitCallback(); + + /** + * Calls back to |oldChan| as described in Init() + */ + void ExplicitCallback(nsresult result); + +private: + ~nsAsyncRedirectVerifyHelper(); + + bool IsOldChannelCanceled(); +}; + +/* + * Helper to make the call-stack handle some control-flow for us + */ +class nsAsyncRedirectAutoCallback +{ +public: + explicit nsAsyncRedirectAutoCallback(nsIAsyncVerifyRedirectCallback* aCallback) + : mCallback(aCallback) + { + mResult = NS_OK; + } + ~nsAsyncRedirectAutoCallback() + { + if (mCallback) + mCallback->OnRedirectVerifyCallback(mResult); + } + /* + * Call this is you want it to call back with a different result-code + */ + void SetResult(nsresult aRes) + { + mResult = aRes; + } + /* + * Call this is you want to avoid the callback + */ + void DontCallback() + { + mCallback = nullptr; + } +private: + nsIAsyncVerifyRedirectCallback* mCallback; + nsresult mResult; +}; + +} // namespace net +} // namespace mozilla +#endif diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp new file mode 100644 index 000000000..6eec29d61 --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.cpp @@ -0,0 +1,419 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAsyncStreamCopier.h" +#include "nsIOService.h" +#include "nsIEventTarget.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsIBufferedStreams.h" +#include "nsIRequestObserver.h" +#include "mozilla/Logging.h" + +using namespace mozilla; + +#undef LOG +// +// MOZ_LOG=nsStreamCopier:5 +// +static LazyLogModule gStreamCopierLog("nsStreamCopier"); +#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args) + +/** + * An event used to perform initialization off the main thread. + */ +class AsyncApplyBufferingPolicyEvent final: public Runnable +{ +public: + /** + * @param aCopier + * The nsAsyncStreamCopier requesting the information. + */ + explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier) + : mCopier(aCopier) + , mTarget(NS_GetCurrentThread()) + { } + NS_IMETHOD Run() override + { + nsresult rv = mCopier->ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + return NS_OK; + } + + rv = mTarget->Dispatch(NewRunnableMethod(mCopier, + &nsAsyncStreamCopier::AsyncCopyInternal), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + } + return NS_OK; + } +private: + RefPtr mCopier; + nsCOMPtr mTarget; +}; + + + +//----------------------------------------------------------------------------- + +nsAsyncStreamCopier::nsAsyncStreamCopier() + : mLock("nsAsyncStreamCopier.mLock") + , mMode(NS_ASYNCCOPY_VIA_READSEGMENTS) + , mChunkSize(nsIOService::gDefaultSegmentSize) + , mStatus(NS_OK) + , mIsPending(false) + , mShouldSniffBuffering(false) +{ + LOG(("Creating nsAsyncStreamCopier @%x\n", this)); +} + +nsAsyncStreamCopier::~nsAsyncStreamCopier() +{ + LOG(("Destroying nsAsyncStreamCopier @%x\n", this)); +} + +bool +nsAsyncStreamCopier::IsComplete(nsresult *status) +{ + MutexAutoLock lock(mLock); + if (status) + *status = mStatus; + return !mIsPending; +} + +nsIRequest* +nsAsyncStreamCopier::AsRequest() +{ + return static_cast(static_cast(this)); +} + +void +nsAsyncStreamCopier::Complete(nsresult status) +{ + LOG(("nsAsyncStreamCopier::Complete [this=%p status=%x]\n", this, 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=%x]\n", status)); + observer->OnStopRequest(AsRequest(), ctx, status); + } +} + +void +nsAsyncStreamCopier::OnAsyncCopyComplete(void *closure, nsresult status) +{ + nsAsyncStreamCopier *self = (nsAsyncStreamCopier *) closure; + self->Complete(status); + NS_RELEASE(self); // addref'd in AsyncCopy +} + +//----------------------------------------------------------------------------- +// nsISupports + +// We cannot use simply NS_IMPL_ISUPPORTSx as both +// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest + +NS_IMPL_ADDREF(nsAsyncStreamCopier) +NS_IMPL_RELEASE(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_BEGIN +NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2) +NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +//----------------------------------------------------------------------------- +// nsIRequest + +NS_IMETHODIMP +nsAsyncStreamCopier::GetName(nsACString &name) +{ + name.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::IsPending(bool *result) +{ + *result = !IsComplete(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetStatus(nsresult *status) +{ + IsComplete(status); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Cancel(nsresult status) +{ + nsCOMPtr copierCtx; + { + MutexAutoLock lock(mLock); + if (!mIsPending) + return NS_OK; + copierCtx.swap(mCopierCtx); + } + + if (NS_SUCCEEDED(status)) { + NS_WARNING("cancel with non-failure status code"); + status = NS_BASE_STREAM_CLOSED; + } + + if (copierCtx) + NS_CancelAsyncCopy(copierCtx, status); + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Suspend() +{ + NS_NOTREACHED("nsAsyncStreamCopier::Suspend"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Resume() +{ + NS_NOTREACHED("nsAsyncStreamCopier::Resume"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup *aLoadGroup) +{ + return NS_OK; +} + +nsresult +nsAsyncStreamCopier::InitInternal(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + NS_ASSERTION(!mSource && !mSink, "Init() called more than once"); + if (chunkSize == 0) { + chunkSize = nsIOService::gDefaultSegmentSize; + } + mChunkSize = chunkSize; + + mSource = source; + mSink = sink; + mCloseSource = closeSource; + mCloseSink = closeSink; + + if (target) { + mTarget = target; + } else { + nsresult rv; + mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + bool sourceBuffered, + bool sinkBuffered, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + NS_ASSERTION(sourceBuffered || sinkBuffered, "at least one stream must be buffered"); + mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS + : NS_ASYNCCOPY_VIA_WRITESEGMENTS; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + mShouldSniffBuffering = true; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +/** + * Detect whether the input or the output stream is buffered, + * bufferize one of them if neither is buffered. + */ +nsresult +nsAsyncStreamCopier::ApplyBufferingPolicy() +{ + // This function causes I/O, it must not be executed on the main + // thread. + MOZ_ASSERT(!NS_IsMainThread()); + + if (NS_OutputStreamIsBuffered(mSink)) { + // Sink is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + return NS_OK; + } + if (NS_InputStreamIsBuffered(mSource)) { + // Source is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_READSEGMENTS; + return NS_OK; + } + + // No buffering, let's buffer the sink + nsresult rv; + nsCOMPtr sink = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = sink->Init(mSink, mChunkSize); + if (NS_FAILED(rv)) { + return rv; + } + + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + mSink = sink; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx) +{ + LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%x]\n", this, observer)); + + NS_ASSERTION(mSource && mSink, "not initialized"); + nsresult rv; + + if (observer) { + // build proxy for observer events + rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx); + if (NS_FAILED(rv)) return rv; + } + + // from this point forward, AsyncCopy is going to return NS_OK. any errors + // will be reported via OnStopRequest. + mIsPending = true; + + if (mObserver) { + rv = mObserver->OnStartRequest(AsRequest(), nullptr); + if (NS_FAILED(rv)) + Cancel(rv); + } + + if (!mShouldSniffBuffering) { + // No buffer sniffing required, let's proceed + AsyncCopyInternal(); + return NS_OK; + } + + if (NS_IsMainThread()) { + // Don't perform buffer sniffing on the main thread + nsCOMPtr event = new AsyncApplyBufferingPolicyEvent(this); + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + Cancel(rv); + } + return NS_OK; + } + + // We're not going to block the main thread, so let's sniff here + rv = ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + Cancel(rv); + } + AsyncCopyInternal(); + return NS_OK; +} + +// Launch async copy. +// All errors are reported through the observer. +void +nsAsyncStreamCopier::AsyncCopyInternal() +{ + MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS + || mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS); + + nsresult rv; + // we want to receive progress notifications; release happens in + // OnAsyncCopyComplete. + NS_ADDREF_THIS(); + { + MutexAutoLock lock(mLock); + rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize, + OnAsyncCopyComplete, this, mCloseSource, mCloseSink, + getter_AddRefs(mCopierCtx)); + } + if (NS_FAILED(rv)) { + NS_RELEASE_THIS(); + Cancel(rv); + } +} + + diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h new file mode 100644 index 000000000..7529a327a --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.h @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncStreamCopier_h__ +#define nsAsyncStreamCopier_h__ + +#include "nsIAsyncStreamCopier.h" +#include "nsIAsyncStreamCopier2.h" +#include "mozilla/Mutex.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" + +class nsIRequestObserver; + +//----------------------------------------------------------------------------- + +class nsAsyncStreamCopier final : public nsIAsyncStreamCopier, + nsIAsyncStreamCopier2 +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIASYNCSTREAMCOPIER + + // nsIAsyncStreamCopier2 + // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2 + // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier + NS_IMETHOD Init(nsIInputStream *aSource, + nsIOutputStream *aSink, + nsIEventTarget *aTarget, + uint32_t aChunkSize, + bool aCloseSource, + bool aCloseSink) override; + + nsAsyncStreamCopier(); + + //------------------------------------------------------------------------- + // these methods may be called on any thread + + bool IsComplete(nsresult *status = nullptr); + void Complete(nsresult status); + +private: + virtual ~nsAsyncStreamCopier(); + + nsresult InitInternal(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink); + + static void OnAsyncCopyComplete(void *, nsresult); + + void AsyncCopyInternal(); + nsresult ApplyBufferingPolicy(); + nsIRequest* AsRequest(); + + nsCOMPtr mSource; + nsCOMPtr mSink; + + nsCOMPtr mObserver; + + nsCOMPtr mTarget; + + nsCOMPtr mCopierCtx; + + mozilla::Mutex mLock; + + nsAsyncCopyMode mMode; + uint32_t mChunkSize; + nsresult mStatus; + bool mIsPending; + bool mCloseSource; + bool mCloseSink; + bool mShouldSniffBuffering; + + friend class ProceedWithAsyncCopy; + friend class AsyncApplyBufferingPolicyEvent; +}; + +#endif // !nsAsyncStreamCopier_h__ diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp new file mode 100644 index 000000000..f52ff5454 --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.cpp @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAuthInformationHolder.h" + +NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation) + +NS_IMETHODIMP +nsAuthInformationHolder::GetFlags(uint32_t* aFlags) +{ + *aFlags = mFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetRealm(nsAString& aRealm) +{ + aRealm = mRealm; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme) +{ + aScheme = mAuthType; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetUsername(nsAString& aUserName) +{ + aUserName = mUser; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetUsername(const nsAString& aUserName) +{ + if (!(mFlags & ONLY_PASSWORD)) + mUser = aUserName; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetPassword(nsAString& aPassword) +{ + aPassword = mPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetPassword(const nsAString& aPassword) +{ + mPassword = aPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetDomain(nsAString& aDomain) +{ + aDomain = mDomain; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetDomain(const nsAString& aDomain) +{ + if (mFlags & NEED_DOMAIN) + mDomain = aDomain; + return NS_OK; +} + + diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h new file mode 100644 index 000000000..b24bc743f --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.h @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef NSAUTHINFORMATIONHOLDER_H_ +#define NSAUTHINFORMATIONHOLDER_H_ + +#include "nsIAuthInformation.h" +#include "nsString.h" + +class nsAuthInformationHolder : public nsIAuthInformation { + +protected: + virtual ~nsAuthInformationHolder() {} + +public: + // aAuthType must be ASCII + nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm, + const nsCString& aAuthType) + : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIAUTHINFORMATION + + const nsString& User() const { return mUser; } + const nsString& Password() const { return mPassword; } + const nsString& Domain() const { return mDomain; } + + /** + * This method can be used to initialize the username when the + * ONLY_PASSWORD flag is set. + */ + void SetUserInternal(const nsString& aUsername) { + mUser = aUsername; + } +private: + nsString mUser; + nsString mPassword; + nsString mDomain; + + uint32_t mFlags; + nsString mRealm; + nsCString mAuthType; +}; + + +#endif diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp new file mode 100644 index 000000000..f112be750 --- /dev/null +++ b/netwerk/base/nsBase64Encoder.cpp @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBase64Encoder.h" + +#include "plbase64.h" +#include "prmem.h" + +NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream) + +NS_IMETHODIMP +nsBase64Encoder::Close() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + mData.Append(aBuf, aCount); + *_retval = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount, + uint32_t* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +nsresult +nsBase64Encoder::Finish(nsCSubstring& result) +{ + char* b64 = PL_Base64Encode(mData.get(), mData.Length(), nullptr); + if (!b64) + return NS_ERROR_OUT_OF_MEMORY; + + result.Assign(b64); + PR_Free(b64); + // Free unneeded memory and allow reusing the object + mData.Truncate(); + return NS_OK; +} diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h new file mode 100644 index 000000000..ff61de51e --- /dev/null +++ b/netwerk/base/nsBase64Encoder.h @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSBASE64ENCODER_H_ +#define NSBASE64ENCODER_H_ + +#include "nsIOutputStream.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +/** + * A base64 encoder. Usage: Instantiate class, write to it using + * Write(), then call Finish() to get the base64-encoded data. + */ +class nsBase64Encoder final : public nsIOutputStream { + public: + nsBase64Encoder() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsresult Finish(nsCSubstring& _result); + private: + ~nsBase64Encoder() {} + + /// The data written to this stream. nsCString can deal fine with + /// binary data. + nsCString mData; +}; + +#endif diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp new file mode 100644 index 000000000..200804c1e --- /dev/null +++ b/netwerk/base/nsBaseChannel.cpp @@ -0,0 +1,937 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseChannel.h" +#include "nsContentUtils.h" +#include "nsURLHelper.h" +#include "nsNetCID.h" +#include "nsMimeTypes.h" +#include "nsIContentSniffer.h" +#include "nsIScriptSecurityManager.h" +#include "nsMimeTypes.h" +#include "nsIHttpEventSink.h" +#include "nsIHttpChannel.h" +#include "nsIChannelEventSink.h" +#include "nsIStreamConverterService.h" +#include "nsChannelClassifier.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsProxyRelease.h" +#include "nsXULAppAPI.h" +#include "nsContentSecurityManager.h" +#include "LoadInfo.h" +#include "nsServiceManagerUtils.h" + +// This class is used to suspend a request across a function scope. +class ScopedRequestSuspender { +public: + explicit ScopedRequestSuspender(nsIRequest *request) + : mRequest(request) { + if (mRequest && NS_FAILED(mRequest->Suspend())) { + NS_WARNING("Couldn't suspend pump"); + mRequest = nullptr; + } + } + ~ScopedRequestSuspender() { + if (mRequest) + mRequest->Resume(); + } +private: + nsIRequest *mRequest; +}; + +// Used to suspend data events from mPump within a function scope. This is +// usually needed when a function makes callbacks that could process events. +#define SUSPEND_PUMP_FOR_SCOPE() \ + ScopedRequestSuspender pump_suspender__(mPump) + +//----------------------------------------------------------------------------- +// nsBaseChannel + +nsBaseChannel::nsBaseChannel() + : mLoadFlags(LOAD_NORMAL) + , mQueriedProgressSink(true) + , mSynthProgressEvents(false) + , mAllowThreadRetargeting(true) + , mWaitingOnAsyncRedirect(false) + , mStatus(NS_OK) + , mContentDispositionHint(UINT32_MAX) + , mContentLength(-1) + , mWasOpened(false) +{ + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsBaseChannel::~nsBaseChannel() +{ + NS_ReleaseOnMainThread(mLoadInfo.forget()); +} + +nsresult +nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags, + bool openNewChannel) +{ + SUSPEND_PUMP_FOR_SCOPE(); + + // Transfer properties + + newChannel->SetLoadGroup(mLoadGroup); + newChannel->SetNotificationCallbacks(mCallbacks); + newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE); + + // make a copy of the loadinfo, append to the redirectchain + // and set it on the new channel + if (mLoadInfo) { + nsSecurityFlags secFlags = mLoadInfo->GetSecurityFlags() & + ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + nsCOMPtr newLoadInfo = + static_cast(mLoadInfo.get())->CloneWithNewSecFlags(secFlags); + + nsCOMPtr uriPrincipal; + nsIScriptSecurityManager *sm = nsContentUtils::GetSecurityManager(); + sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal)); + bool isInternalRedirect = + (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE)); + newLoadInfo->AppendRedirectedPrincipal(uriPrincipal, isInternalRedirect); + newChannel->SetLoadInfo(newLoadInfo); + } + else { + // the newChannel was created with a dummy loadInfo, we should clear + // it in case the original channel does not have a loadInfo + newChannel->SetLoadInfo(nullptr); + } + + // Preserve the privacy bit if it has been overridden + if (mPrivateBrowsingOverriden) { + nsCOMPtr newPBChannel = + do_QueryInterface(newChannel); + if (newPBChannel) { + newPBChannel->SetPrivate(mPrivateBrowsing); + } + } + + nsCOMPtr bag = ::do_QueryInterface(newChannel); + if (bag) { + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + bag->SetProperty(iter.Key(), iter.UserData()); + } + } + + // Notify consumer, giving chance to cancel redirect. For backwards compat, + // we support nsIHttpEventSink if we are an HTTP channel and if this is not + // an internal redirect. + + RefPtr redirectCallbackHelper = + new nsAsyncRedirectVerifyHelper(); + + bool checkRedirectSynchronously = !openNewChannel; + + mRedirectChannel = newChannel; + mRedirectFlags = redirectFlags; + mOpenRedirectChannel = openNewChannel; + nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags, + checkRedirectSynchronously); + if (NS_FAILED(rv)) + return rv; + + if (checkRedirectSynchronously && NS_FAILED(mStatus)) + return mStatus; + + return NS_OK; +} + +nsresult +nsBaseChannel::ContinueRedirect() +{ + // Backwards compat for non-internal redirects from a HTTP channel. + // XXX Is our http channel implementation going to derive from nsBaseChannel? + // If not, this code can be removed. + if (!(mRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) { + nsCOMPtr httpChannel = do_QueryInterface(); + if (httpChannel) { + nsCOMPtr httpEventSink; + GetCallback(httpEventSink); + if (httpEventSink) { + nsresult rv = httpEventSink->OnRedirect(httpChannel, mRedirectChannel); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + + // Make sure to do this _after_ making all the OnChannelRedirect calls + mRedirectChannel->SetOriginalURI(OriginalURI()); + + // If we fail to open the new channel, then we want to leave this channel + // unaffected, so we defer tearing down our channel until we have succeeded + // with the redirect. + + if (mOpenRedirectChannel) { + nsresult rv = NS_OK; + if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) { + MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!"); + rv = mRedirectChannel->AsyncOpen2(mListener); + } + else { + rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); + } + NS_ENSURE_SUCCESS(rv, rv); + } + + mRedirectChannel = nullptr; + + // close down this channel + Cancel(NS_BINDING_REDIRECTED); + ChannelDone(); + + return NS_OK; +} + +bool +nsBaseChannel::HasContentTypeHint() const +{ + NS_ASSERTION(!Pending(), "HasContentTypeHint called too late"); + return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsresult +nsBaseChannel::PushStreamConverter(const char *fromType, + const char *toType, + bool invalidatesContentLength, + nsIStreamListener **result) +{ + NS_ASSERTION(mListener, "no listener"); + + nsresult rv; + nsCOMPtr scs = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr converter; + rv = scs->AsyncConvertData(fromType, toType, mListener, mListenerContext, + getter_AddRefs(converter)); + if (NS_SUCCEEDED(rv)) { + mListener = converter; + if (invalidatesContentLength) + mContentLength = -1; + if (result) { + *result = nullptr; + converter.swap(*result); + } + } + return rv; +} + +nsresult +nsBaseChannel::BeginPumpingData() +{ + nsCOMPtr stream; + nsCOMPtr channel; + nsresult rv = OpenContentStream(true, getter_AddRefs(stream), + getter_AddRefs(channel)); + if (NS_FAILED(rv)) + return rv; + + NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?"); + + if (channel) { + rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel)); + if (NS_SUCCEEDED(rv)) + mWaitingOnAsyncRedirect = true; + return rv; + } + + // By assigning mPump, we flag this channel as pending (see Pending). It's + // important that the pending flag is set when we call into the stream (the + // call to AsyncRead results in the stream's AsyncWait method being called) + // and especially when we call into the loadgroup. Our caller takes care to + // release mPump if we return an error. + + rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0, + true); + if (NS_SUCCEEDED(rv)) + rv = mPump->AsyncRead(this, nullptr); + + return rv; +} + +void +nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) +{ + NS_ASSERTION(!mPump, "Shouldn't have gotten here"); + + nsresult rv = mStatus; + if (NS_SUCCEEDED(mStatus)) { + rv = Redirect(newChannel, + nsIChannelEventSink::REDIRECT_TEMPORARY, + true); + if (NS_SUCCEEDED(rv)) { + // OnRedirectVerifyCallback will be called asynchronously + return; + } + } + + ContinueHandleAsyncRedirect(rv); +} + +void +nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result) +{ + mWaitingOnAsyncRedirect = false; + + if (NS_FAILED(result)) + Cancel(result); + + if (NS_FAILED(result) && mListener) { + // Notify our consumer ourselves + mListener->OnStartRequest(this, mListenerContext); + mListener->OnStopRequest(this, mListenerContext, mStatus); + ChannelDone(); + } + + if (mLoadGroup) + mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); +} + +void +nsBaseChannel::ClassifyURI() +{ + // For channels created in the child process, delegate to the parent to + // classify URIs. + if (!XRE_IsParentProcess()) { + return; + } + + if (mLoadFlags & LOAD_CLASSIFY_URI) { + RefPtr classifier = new nsChannelClassifier(); + if (classifier) { + classifier->Start(this); + } else { + Cancel(NS_ERROR_OUT_OF_MEMORY); + } + } +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsISupports + +NS_IMPL_ISUPPORTS_INHERITED(nsBaseChannel, + nsHashPropertyBag, + nsIRequest, + nsIChannel, + nsIThreadRetargetableRequest, + nsIInterfaceRequestor, + nsITransportEventSink, + nsIRequestObserver, + nsIStreamListener, + nsIThreadRetargetableStreamListener, + nsIAsyncVerifyRedirectCallback, + nsIPrivateBrowsingChannel) + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequest + +NS_IMETHODIMP +nsBaseChannel::GetName(nsACString &result) +{ + if (!mURI) { + result.Truncate(); + return NS_OK; + } + return mURI->GetSpec(result); +} + +NS_IMETHODIMP +nsBaseChannel::IsPending(bool *result) +{ + *result = Pending(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetStatus(nsresult *status) +{ + if (mPump && NS_SUCCEEDED(mStatus)) { + mPump->GetStatus(status); + } else { + *status = mStatus; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Cancel(nsresult status) +{ + // Ignore redundant cancelation + if (NS_FAILED(mStatus)) + return NS_OK; + + mStatus = status; + + if (mPump) + mPump->Cancel(status); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Suspend() +{ + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsBaseChannel::Resume() +{ + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + return mPump->Resume(); +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) +{ + if (!CanSetLoadGroup(aLoadGroup)) { + return NS_ERROR_FAILURE; + } + + mLoadGroup = aLoadGroup; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIChannel + +NS_IMETHODIMP +nsBaseChannel::GetOriginalURI(nsIURI **aURI) +{ + *aURI = OriginalURI(); + NS_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOriginalURI(nsIURI *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetURI(nsIURI **aURI) +{ + NS_IF_ADDREF(*aURI = mURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetOwner(nsISupports **aOwner) +{ + NS_IF_ADDREF(*aOwner = mOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOwner(nsISupports *aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) +{ + NS_IF_ADDREF(*aCallbacks = mCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) +{ + if (!CanSetCallbacks(aCallbacks)) { + return NS_ERROR_FAILURE; + } + + mCallbacks = aCallbacks; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetSecurityInfo(nsISupports **aSecurityInfo) +{ + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentType(nsACString &aContentType) +{ + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentType(const nsACString &aContentType) +{ + // mContentCharset is unchanged if not parsed + bool dummy; + net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentCharset(nsACString &aContentCharset) +{ + aContentCharset = mContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentCharset(const nsACString &aContentCharset) +{ + mContentCharset = aContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDisposition(uint32_t *aContentDisposition) +{ + // preserve old behavior, fail unless explicitly set. + if (mContentDispositionHint == UINT32_MAX) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aContentDisposition = mContentDispositionHint; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + mContentDispositionHint = aContentDisposition; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) +{ + if (!mContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; + } + + aContentDispositionFilename = *mContentDispositionFilename; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) +{ + mContentDispositionFilename = new nsString(aContentDispositionFilename); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentLength(int64_t *aContentLength) +{ + *aContentLength = mContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentLength(int64_t aContentLength) +{ + mContentLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Open(nsIInputStream **result) +{ + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS); + + nsCOMPtr chan; + nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan)); + NS_ASSERTION(!chan || !*result, "Got both a channel and a stream?"); + if (NS_SUCCEEDED(rv) && chan) { + rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false); + if (NS_FAILED(rv)) + return rv; + rv = chan->Open(result); + } else if (rv == NS_ERROR_NOT_IMPLEMENTED) + return NS_ImplementChannelOpen(this, result); + + if (NS_SUCCEEDED(rv)) { + mWasOpened = true; + ClassifyURI(); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + NS_ENSURE_ARG(listener); + + // Skip checking for chrome:// sub-resources. + nsAutoCString scheme; + mURI->GetScheme(scheme); + if (!scheme.EqualsLiteral("file")) { + NS_CompareLoadInfoAndLoadContext(this); + } + + // Ensure that this is an allowed port before proceeding. + nsresult rv = NS_CheckPortSafety(mURI); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + // Store the listener and context early so that OpenContentStream and the + // stream's AsyncWait method (called by AsyncRead) can have access to them + // via PushStreamConverter and the StreamListener methods. However, since + // this typically introduces a reference cycle between this and the listener, + // we need to be sure to break the reference if this method does not succeed. + mListener = listener; + mListenerContext = ctxt; + + // This method assigns mPump as a side-effect. We need to clear mPump if + // this method fails. + rv = BeginPumpingData(); + if (NS_FAILED(rv)) { + mPump = nullptr; + ChannelDone(); + mCallbacks = nullptr; + return rv; + } + + // At this point, we are going to return success no matter what. + + mWasOpened = true; + + SUSPEND_PUMP_FOR_SCOPE(); + + if (mLoadGroup) + mLoadGroup->AddRequest(this, nullptr); + + ClassifyURI(); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsITransportEventSink + +NS_IMETHODIMP +nsBaseChannel::OnTransportStatus(nsITransport *transport, nsresult status, + int64_t progress, int64_t progressMax) +{ + // In some cases, we may wish to suppress transport-layer status events. + + if (!mPump || NS_FAILED(mStatus)) { + return NS_OK; + } + + SUSPEND_PUMP_FOR_SCOPE(); + + // Lazily fetch mProgressSink + if (!mProgressSink) { + if (mQueriedProgressSink) { + return NS_OK; + } + GetCallback(mProgressSink); + mQueriedProgressSink = true; + if (!mProgressSink) { + return NS_OK; + } + } + + if (!HasLoadFlag(LOAD_BACKGROUND)) { + nsAutoString statusArg; + if (GetStatusArg(status, statusArg)) { + mProgressSink->OnStatus(this, mListenerContext, status, statusArg.get()); + } + } + + if (progress) { + mProgressSink->OnProgress(this, mListenerContext, progress, progressMax); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIInterfaceRequestor + +NS_IMETHODIMP +nsBaseChannel::GetInterface(const nsIID &iid, void **result) +{ + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result); + return *result ? NS_OK : NS_ERROR_NO_INTERFACE; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequestObserver + +static void +CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount) +{ + nsIChannel *chan = static_cast(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); + + nsCOMPtr sniffer = + do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER); + if (!sniffer) + return; + + nsAutoCString detected; + nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected); + if (NS_SUCCEEDED(rv)) + chan->SetContentType(detected); +} + +NS_IMETHODIMP +nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + MOZ_ASSERT(request == mPump); + + // If our content type is unknown, use the content type + // sniffer. If the sniffer is not available for some reason, then we just keep + // going as-is. + if (NS_SUCCEEDED(mStatus) && + mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { + mPump->PeekStream(CallUnknownTypeSniffer, static_cast(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, mListenerContext); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + // If both mStatus and status are failure codes, we keep mStatus as-is since + // that is consistent with our GetStatus and Cancel methods. + if (NS_SUCCEEDED(mStatus)) + mStatus = status; + + // Cause Pending to return false. + mPump = nullptr; + + if (mListener) // null in case of redirect + mListener->OnStopRequest(this, mListenerContext, mStatus); + ChannelDone(); + + // No need to suspend pump in this scope since we will not be receiving + // any more events from it. + + if (mLoadGroup) + mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIStreamListener + +NS_IMETHODIMP +nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *stream, uint64_t offset, + uint32_t count) +{ + SUSPEND_PUMP_FOR_SCOPE(); + + nsresult rv = mListener->OnDataAvailable(this, mListenerContext, stream, + offset, count); + if (mSynthProgressEvents && NS_SUCCEEDED(rv)) { + int64_t prog = offset + count; + if (NS_IsMainThread()) { + OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength); + } else { + class OnTransportStatusAsyncEvent : public mozilla::Runnable + { + RefPtr mChannel; + int64_t mProgress; + int64_t mContentLength; + public: + OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, + int64_t aProgress, + int64_t aContentLength) + : mChannel(aChannel), + mProgress(aProgress), + mContentLength(aContentLength) + { } + + NS_IMETHOD Run() override + { + return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING, + mProgress, mContentLength); + } + }; + + nsCOMPtr runnable = + new OnTransportStatusAsyncEvent(this, prog, mContentLength); + NS_DispatchToMainThread(runnable); + } + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::OnRedirectVerifyCallback(nsresult result) +{ + if (NS_SUCCEEDED(result)) + result = ContinueRedirect(); + + if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) { + if (NS_SUCCEEDED(mStatus)) + mStatus = result; + return NS_OK; + } + + if (mWaitingOnAsyncRedirect) + ContinueHandleAsyncRedirect(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget) +{ + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + return mPump->RetargetDeliveryTo(aEventTarget); +} + +NS_IMETHODIMP +nsBaseChannel::CheckListenerChain() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr listener = + do_QueryInterface(mListener); + if (!listener) { + return NS_ERROR_NO_INTERFACE; + } + + return listener->CheckListenerChain(); +} diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h new file mode 100644 index 000000000..b98609e85 --- /dev/null +++ b/netwerk/base/nsBaseChannel.h @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseChannel_h__ +#define nsBaseChannel_h__ + +#include "nsString.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsHashPropertyBag.h" +#include "nsInputStreamPump.h" + +#include "nsIChannel.h" +#include "nsIURI.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIStreamListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIProgressEventSink.h" +#include "nsITransport.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "PrivateBrowsingChannel.h" +#include "nsThreadUtils.h" + +class nsIInputStream; + +//----------------------------------------------------------------------------- +// nsBaseChannel is designed to be subclassed. The subclass is responsible for +// implementing the OpenContentStream method, which will be called by the +// nsIChannel::AsyncOpen and nsIChannel::Open implementations. +// +// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way +// for subclasses to query both the nsIChannel::notificationCallbacks and +// nsILoadGroup::notificationCallbacks for supported interfaces. +// +// nsBaseChannel implements nsITransportEventSink to support progress & status +// notifications generated by the transport layer. + +class nsBaseChannel : public nsHashPropertyBag + , public nsIChannel + , public nsIThreadRetargetableRequest + , public nsIInterfaceRequestor + , public nsITransportEventSink + , public nsIAsyncVerifyRedirectCallback + , public mozilla::net::PrivateBrowsingChannel + , protected nsIStreamListener + , protected nsIThreadRetargetableStreamListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSITHREADRETARGETABLEREQUEST + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsBaseChannel(); + + // This method must be called to initialize the basechannel instance. + nsresult Init() { + return NS_OK; + } + +protected: + // ----------------------------------------------- + // Methods to be implemented by the derived class: + + virtual ~nsBaseChannel(); + +private: + // Implemented by subclass to supply data stream. The parameter, async, is + // true when called from nsIChannel::AsyncOpen and false otherwise. When + // async is true, the resulting stream will be used with a nsIInputStreamPump + // instance. This means that if it is a non-blocking stream that supports + // nsIAsyncInputStream that it will be read entirely on the main application + // thread, and its AsyncWait method will be called whenever ReadSegments + // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking, + // then it will be read on one of the background I/O threads, and it does not + // need to implement ReadSegments. If async is false, this method may return + // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in + // terms of AsyncOpen (see NS_ImplementChannelOpen). + // A callee is allowed to return an nsIChannel instead of an nsIInputStream. + // That case will be treated as a redirect to the new channel. By default + // *channel will be set to null by the caller, so callees who don't want to + // return one an just not touch it. + virtual nsresult OpenContentStream(bool async, nsIInputStream **stream, + nsIChannel** channel) = 0; + + // The basechannel calls this method from its OnTransportStatus method to + // determine whether to call nsIProgressEventSink::OnStatus in addition to + // nsIProgressEventSink::OnProgress. This method may be overriden by the + // subclass to enable nsIProgressEventSink::OnStatus events. If this method + // returns true, then the statusArg out param specifies the "statusArg" value + // to pass to the OnStatus method. By default, OnStatus messages are + // suppressed. The status parameter passed to this method is the status value + // from the OnTransportStatus method. + virtual bool GetStatusArg(nsresult status, nsString &statusArg) { + return false; + } + + // Called when the callbacks available to this channel may have changed. + virtual void OnCallbacksChanged() { + } + + // Called when our channel is done, to allow subclasses to drop resources. + virtual void OnChannelDone() { + } + +public: + // ---------------------------------------------- + // Methods provided for use by the derived class: + + // Redirect to another channel. This method takes care of notifying + // observers of this redirect as well as of opening the new channel, if asked + // to do so. It also cancels |this| with the status code + // NS_BINDING_REDIRECTED. A failure return from this method means that the + // redirect could not be performed (no channel was opened; this channel + // wasn't canceled.) The redirectFlags parameter consists of the flag values + // defined on nsIChannelEventSink. + nsresult Redirect(nsIChannel *newChannel, uint32_t redirectFlags, + bool openNewChannel); + + // Tests whether a type hint was set. Subclasses can use this to decide + // whether to call SetContentType. + // NOTE: This is only reliable if the subclass didn't itself call + // SetContentType, and should also not be called after OpenContentStream. + bool HasContentTypeHint() const; + + // The URI member should be initialized before the channel is used, and then + // it should never be changed again until the channel is destroyed. + nsIURI *URI() { + return mURI; + } + void SetURI(nsIURI *uri) { + NS_ASSERTION(uri, "must specify a non-null URI"); + NS_ASSERTION(!mURI, "must not modify URI"); + NS_ASSERTION(!mOriginalURI, "how did that get set so early?"); + mURI = uri; + mOriginalURI = uri; + } + nsIURI *OriginalURI() { + return mOriginalURI; + } + + // The security info is a property of the transport-layer, which should be + // assigned by the subclass. + nsISupports *SecurityInfo() { + return mSecurityInfo; + } + void SetSecurityInfo(nsISupports *info) { + mSecurityInfo = info; + } + + // Test the load flags + bool HasLoadFlag(uint32_t flag) { + return (mLoadFlags & flag) != 0; + } + + // This is a short-cut to calling nsIRequest::IsPending() + virtual bool Pending() const { + return mPump || mWaitingOnAsyncRedirect; + } + + // Helper function for querying the channel's notification callbacks. + template void GetCallback(nsCOMPtr &result) { + GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result)); + } + + // Helper function for calling QueryInterface on this. + nsQueryInterface do_QueryInterface() { + return nsQueryInterface(static_cast(this)); + } + // MSVC needs this: + nsQueryInterface do_QueryInterface(nsISupports *obj) { + return nsQueryInterface(obj); + } + + // If a subclass does not want to feed transport-layer progress events to the + // base channel via nsITransportEventSink, then it may set this flag to cause + // the base channel to synthesize progress events when it receives data from + // the content stream. By default, progress events are not synthesized. + void EnableSynthesizedProgressEvents(bool enable) { + mSynthProgressEvents = enable; + } + + // Some subclasses may wish to manually insert a stream listener between this + // and the channel's listener. The following methods make that possible. + void SetStreamListener(nsIStreamListener *listener) { + mListener = listener; + } + nsIStreamListener *StreamListener() { + return mListener; + } + + // Pushes a new stream converter in front of the channel's stream listener. + // The fromType and toType values are passed to nsIStreamConverterService's + // AsyncConvertData method. If invalidatesContentLength is true, then the + // channel's content-length property will be assigned a value of -1. This is + // necessary when the converter changes the length of the resulting data + // stream, which is almost always the case for a "stream converter" ;-) + // This function optionally returns a reference to the new converter. + nsresult PushStreamConverter(const char *fromType, const char *toType, + bool invalidatesContentLength = true, + nsIStreamListener **converter = nullptr); + +protected: + void DisallowThreadRetargeting() { + mAllowThreadRetargeting = false; + } + +private: + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + // Called to setup mPump and call AsyncRead on it. + nsresult BeginPumpingData(); + + // Called when the callbacks available to this channel may have changed. + void CallbacksChanged() { + mProgressSink = nullptr; + mQueriedProgressSink = false; + OnCallbacksChanged(); + } + + // Called when our channel is done. This should drop no-longer-needed pointers. + void ChannelDone() { + mListener = nullptr; + mListenerContext = nullptr; + OnChannelDone(); + } + + // Handle an async redirect callback. This will only be called if we + // returned success from AsyncOpen while posting a redirect runnable. + void HandleAsyncRedirect(nsIChannel* newChannel); + void ContinueHandleAsyncRedirect(nsresult result); + nsresult ContinueRedirect(); + + // start URI classifier if requested + void ClassifyURI(); + + class RedirectRunnable : public mozilla::Runnable + { + public: + RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel) + : mChannel(chan), mNewChannel(newChannel) + { + NS_PRECONDITION(newChannel, "Must have channel to redirect to"); + } + + NS_IMETHOD Run() override + { + mChannel->HandleAsyncRedirect(mNewChannel); + return NS_OK; + } + + private: + RefPtr mChannel; + nsCOMPtr mNewChannel; + }; + friend class RedirectRunnable; + + RefPtr mPump; + nsCOMPtr mProgressSink; + nsCOMPtr mOriginalURI; + nsCOMPtr mOwner; + nsCOMPtr mSecurityInfo; + nsCOMPtr mRedirectChannel; + nsCString mContentType; + nsCString mContentCharset; + uint32_t mLoadFlags; + bool mQueriedProgressSink; + bool mSynthProgressEvents; + bool mAllowThreadRetargeting; + bool mWaitingOnAsyncRedirect; + bool mOpenRedirectChannel; + uint32_t mRedirectFlags; + +protected: + nsCOMPtr mURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mLoadInfo; + nsCOMPtr mCallbacks; + nsCOMPtr mListener; + nsCOMPtr mListenerContext; + nsresult mStatus; + uint32_t mContentDispositionHint; + nsAutoPtr mContentDispositionFilename; + int64_t mContentLength; + bool mWasOpened; + + 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 000000000..ee5a8ef3c --- /dev/null +++ b/netwerk/base/nsBaseContentStream.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseContentStream.h" +#include "nsStreamUtils.h" + +//----------------------------------------------------------------------------- + +void +nsBaseContentStream::DispatchCallback(bool async) +{ + if (!mCallback) + return; + + // It's important to clear mCallback and mCallbackTarget up-front because the + // OnInputStreamReady implementation may call our AsyncWait method. + + nsCOMPtr callback; + if (async) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + mCallback = nullptr; + } else { + callback.swap(mCallback); + } + mCallbackTarget = nullptr; + + callback->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsISupports + +NS_IMPL_ADDREF(nsBaseContentStream) +NS_IMPL_RELEASE(nsBaseContentStream) + +// We only support nsIAsyncInputStream when we are in non-blocking mode. +NS_INTERFACE_MAP_BEGIN(nsBaseContentStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END_THREADSAFE + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIInputStream + +NS_IMETHODIMP +nsBaseContentStream::Close() +{ + return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsBaseContentStream::Available(uint64_t *result) +{ + *result = 0; + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::Read(char *buf, uint32_t count, uint32_t *result) +{ + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure, + uint32_t count, uint32_t *result) +{ + *result = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) + return NS_OK; + + // No data yet + if (!IsClosed() && IsNonBlocking()) + return NS_BASE_STREAM_WOULD_BLOCK; + + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::IsNonBlocking(bool *result) +{ + *result = mNonBlocking; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIAsyncInputStream + +NS_IMETHODIMP +nsBaseContentStream::CloseWithStatus(nsresult status) +{ + if (IsClosed()) + return NS_OK; + + NS_ENSURE_ARG(NS_FAILED(status)); + mStatus = status; + + DispatchCallback(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseContentStream::AsyncWait(nsIInputStreamCallback *callback, + uint32_t flags, uint32_t requestedCount, + nsIEventTarget *target) +{ + // Our _only_ consumer is nsInputStreamPump, so we simplify things here by + // making assumptions about how we will be called. + NS_ASSERTION(target, "unexpected parameter"); + NS_ASSERTION(flags == 0, "unexpected parameter"); + NS_ASSERTION(requestedCount == 0, "unexpected parameter"); + +#ifdef DEBUG + bool correctThread; + target->IsOnCurrentThread(&correctThread); + NS_ASSERTION(correctThread, "event target must be on the current thread"); +#endif + + mCallback = callback; + mCallbackTarget = target; + + if (!mCallback) + return NS_OK; + + // If we're already closed, then dispatch this callback immediately. + if (IsClosed()) { + DispatchCallback(); + return NS_OK; + } + + OnCallbackPending(); + return NS_OK; +} diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h new file mode 100644 index 000000000..992c8733e --- /dev/null +++ b/netwerk/base/nsBaseContentStream.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseContentStream_h__ +#define nsBaseContentStream_h__ + +#include "nsIAsyncInputStream.h" +#include "nsIEventTarget.h" +#include "nsCOMPtr.h" + +//----------------------------------------------------------------------------- +// nsBaseContentStream is designed to be subclassed with the intention of being +// used to satisfy the nsBaseChannel::OpenContentStream method. +// +// The subclass typically overrides the default Available, ReadSegments and +// CloseWithStatus methods. By default, Read is implemented in terms of +// ReadSegments, and Close is implemented in terms of CloseWithStatus. If +// CloseWithStatus is overriden, then the subclass will usually want to call +// the base class' CloseWithStatus method before returning. +// +// If the stream is non-blocking, then readSegments may return the exception +// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is +// not at the "end-of-file" or already closed. This error code must not be +// returned from the Available implementation. When the caller receives this +// error code, he may choose to call the stream's AsyncWait method, in which +// case the base stream will have a non-null PendingCallback. When the stream +// has data or encounters an error, it should be sure to dispatch a pending +// callback if one exists (see DispatchCallback). The implementation of the +// base stream's CloseWithStatus (and Close) method will ensure that any +// pending callback is dispatched. It is the responsibility of the subclass +// to ensure that the pending callback is dispatched when it wants to have its +// ReadSegments method called again. + +class nsBaseContentStream : public nsIAsyncInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsBaseContentStream(bool nonBlocking) + : mStatus(NS_OK) + , mNonBlocking(nonBlocking) { + } + + nsresult Status() { return mStatus; } + bool IsNonBlocking() { return mNonBlocking; } + bool IsClosed() { return NS_FAILED(mStatus); } + + // Called to test if the stream has a pending callback. + bool HasPendingCallback() { return mCallback != nullptr; } + + // The current dispatch target (may be null) for the pending callback if any. + nsIEventTarget *CallbackTarget() { return mCallbackTarget; } + + // Called to dispatch a pending callback. If there is no pending callback, + // then this function does nothing. Pass true to this function to cause the + // callback to occur asynchronously; otherwise, the callback will happen + // before this function returns. + void DispatchCallback(bool async = true); + + // Helper function to make code more self-documenting. + void DispatchCallbackSync() { DispatchCallback(false); } + +protected: + virtual ~nsBaseContentStream() {} + +private: + // Called from the base stream's AsyncWait method when a pending callback + // is installed on the stream. + virtual void OnCallbackPending() {} + +private: + nsCOMPtr 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 000000000..e67c3009b --- /dev/null +++ b/netwerk/base/nsBufferedStreams.cpp @@ -0,0 +1,832 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsBufferedStreams.h" +#include "nsStreamUtils.h" +#include "nsNetCID.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include + +#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 mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedStream + +nsBufferedStream::nsBufferedStream() + : mBuffer(nullptr), + mBufferStartOffset(0), + mCursor(0), + mFillPoint(0), + mStream(nullptr), + mBufferDisabled(false), + mEOF(false), + mGetBufferCount(0) +{ +} + +nsBufferedStream::~nsBufferedStream() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsBufferedStream, nsISeekableStream) + +nsresult +nsBufferedStream::Init(nsISupports* stream, uint32_t bufferSize) +{ + NS_ASSERTION(stream, "need to supply a stream"); + NS_ASSERTION(mStream == nullptr, "already inited"); + mStream = stream; + NS_IF_ADDREF(mStream); + mBufferSize = bufferSize; + mBufferStartOffset = 0; + mCursor = 0; + mBuffer = new (mozilla::fallible) char[bufferSize]; + if (mBuffer == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +nsresult +nsBufferedStream::Close() +{ + NS_IF_RELEASE(mStream); + if (mBuffer) { + delete[] mBuffer; + mBuffer = nullptr; + mBufferSize = 0; + mBufferStartOffset = 0; + mCursor = 0; + mFillPoint = 0; + } +#ifdef METERING + { + static FILE *tfp; + if (!tfp) { + tfp = fopen("/tmp/bufstats", "w"); + if (tfp) + setvbuf(tfp, nullptr, _IOLBF, 0); + } + if (tfp) { + fprintf(tfp, "seeks within buffer: %u\n", + bufstats.mSeeksWithinBuffer); + fprintf(tfp, "seeks outside buffer: %u\n", + bufstats.mSeeksOutsideBuffer); + fprintf(tfp, "buffer read on seek: %u\n", + bufstats.mBufferReadUponSeek); + fprintf(tfp, "buffer unread on seek: %u\n", + bufstats.mBufferUnreadUponSeek); + fprintf(tfp, "bytes read from buffer: %u\n", + bufstats.mBytesReadFromBuffer); + for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) { + fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n", + i, + bufstats.mBigSeek[i].mOldOffset, + bufstats.mBigSeek[i].mNewOffset); + } + } + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedStream::Seek(int32_t whence, int64_t offset) +{ + if (mStream == nullptr) + return NS_BASE_STREAM_CLOSED; + + // If the underlying stream isn't a random access store, then fail early. + // We could possibly succeed for the case where the seek position denotes + // something that happens to be read into the buffer, but that would make + // the failure data-dependent. + nsresult rv; + nsCOMPtr ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) return rv; + + int64_t absPos = 0; + switch (whence) { + case nsISeekableStream::NS_SEEK_SET: + absPos = offset; + break; + case nsISeekableStream::NS_SEEK_CUR: + absPos = mBufferStartOffset; + absPos += mCursor; + absPos += offset; + break; + case nsISeekableStream::NS_SEEK_END: + absPos = -1; + break; + default: + NS_NOTREACHED("bogus seek whence parameter"); + return NS_ERROR_UNEXPECTED; + } + + // Let mCursor point into the existing buffer if the new position is + // between the current cursor and the mFillPoint "fencepost" -- the + // client may never get around to a Read or Write after this Seek. + // Read and Write worry about flushing and filling in that event. + // But if we're at EOF, make sure to pass the seek through to the + // underlying stream, because it may have auto-closed itself and + // needs to reopen. + uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint && !mEOF) { + METER(bufstats.mSeeksWithinBuffer++); + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(bufstats.mSeeksOutsideBuffer++); + METER(bufstats.mBufferReadUponSeek += mCursor); + METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor); + rv = Flush(); + if (NS_FAILED(rv)) return rv; + + rv = ras->Seek(whence, offset); + if (NS_FAILED(rv)) return rv; + + mEOF = false; + + // Recompute whether the offset we're seeking to is in our buffer. + // Note that we need to recompute because Flush() might have + // changed mBufferStartOffset. + offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint) { + // It's safe to just set mCursor to offsetInBuffer. In particular, we + // want to avoid calling Fill() here since we already have the data that + // was seeked to and calling Fill() might auto-close our underlying + // stream in some cases. + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex].mOldOffset = + mBufferStartOffset + int64_t(mCursor)); + const int64_t minus1 = -1; + if (absPos == minus1) { + // then we had the SEEK_END case, above + int64_t tellPos; + rv = ras->Tell(&tellPos); + mBufferStartOffset = tellPos; + if (NS_FAILED(rv)) return rv; + } + else { + mBufferStartOffset = absPos; + } + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex++].mNewOffset = + mBufferStartOffset); + + mFillPoint = mCursor = 0; + return Fill(); +} + +NS_IMETHODIMP +nsBufferedStream::Tell(int64_t *result) +{ + if (mStream == nullptr) + return NS_BASE_STREAM_CLOSED; + + int64_t result64 = mBufferStartOffset; + result64 += mCursor; + *result = result64; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedStream::SetEOF() +{ + if (mStream == nullptr) + return NS_BASE_STREAM_CLOSED; + + nsresult rv; + nsCOMPtr ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) return rv; + + rv = ras->SetEOF(); + if (NS_SUCCEEDED(rv)) + mEOF = true; + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedInputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream) + +NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_BUFFEREDINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, + nsIInputStream, + nsIBufferedInputStream, + nsISeekableStream, + nsIStreamBufferAccess) + +nsresult +nsBufferedInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsBufferedInputStream* stream = new nsBufferedInputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) +{ + return nsBufferedStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsBufferedInputStream::Close() +{ + nsresult rv1 = NS_OK, rv2; + if (mStream) { + rv1 = Source()->Close(); + NS_RELEASE(mStream); + } + rv2 = nsBufferedStream::Close(); + if (NS_FAILED(rv1)) return rv1; + return rv2; +} + +NS_IMETHODIMP +nsBufferedInputStream::Available(uint64_t *result) +{ + nsresult rv = NS_OK; + *result = 0; + if (mStream) { + rv = Source()->Available(result); + } + *result += (mFillPoint - mCursor); + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::Read(char * buf, uint32_t count, uint32_t *result) +{ + if (mBufferDisabled) { + if (!mStream) { + *result = 0; + return NS_OK; + } + nsresult rv = Source()->Read(buf, count, result); + if (NS_SUCCEEDED(rv)) { + mBufferStartOffset += *result; // so nsBufferedStream::Tell works + if (*result == 0) { + mEOF = true; + } + } + return rv; + } + + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure, + uint32_t count, uint32_t *result) +{ + *result = 0; + + if (!mStream) + return NS_OK; + + nsresult rv = NS_OK; + while (count > 0) { + uint32_t amt = std::min(count, mFillPoint - mCursor); + if (amt > 0) { + uint32_t read = 0; + rv = writer(this, closure, mBuffer + mCursor, *result, amt, &read); + if (NS_FAILED(rv)) { + // errors returned from the writer end here! + rv = NS_OK; + break; + } + *result += read; + count -= read; + mCursor += read; + } + else { + rv = Fill(); + if (NS_FAILED(rv) || mFillPoint == mCursor) + break; + } + } + return (*result > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::IsNonBlocking(bool *aNonBlocking) +{ + if (mStream) + return Source()->IsNonBlocking(aNonBlocking); + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsBufferedInputStream::Fill() +{ + if (mBufferDisabled) + return NS_OK; + NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED); + + nsresult rv; + int32_t rem = int32_t(mFillPoint - mCursor); + if (rem > 0) { + // slide the remainder down to the start of the buffer + // |<------------->|<--rem-->|<--->| + // b c f s + memcpy(mBuffer, mBuffer + mCursor, rem); + } + mBufferStartOffset += mCursor; + mFillPoint = rem; + mCursor = 0; + + uint32_t amt; + rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt); + if (NS_FAILED(rv)) return rv; + + if (amt == 0) + mEOF = true; + + mFillPoint += amt; + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) + return nullptr; + + if (mBufferDisabled) + return nullptr; + + char* buf = mBuffer + mCursor; + uint32_t rem = mFillPoint - mCursor; + if (rem == 0) { + if (NS_FAILED(Fill())) + return nullptr; + buf = mBuffer + mCursor; + rem = mFillPoint - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) + return nullptr; + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) + return nullptr; + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) + return; + + NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch"); + mCursor += aLength; +} + +NS_IMETHODIMP +nsBufferedInputStream::DisableBuffering() +{ + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) + return NS_ERROR_UNEXPECTED; + + // Empty the buffer so nsBufferedStream::Tell works. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::EnableBuffering() +{ + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::GetUnbufferedStream(nsISupports* *aStream) +{ + // Empty the buffer so subsequent i/o trumps any buffered data. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + + *aStream = mStream; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +void +nsBufferedInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + BufferedInputStreamParams params; + + if (mStream) { + nsCOMPtr stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamParams wrappedParams; + SerializeInputStream(stream, wrappedParams, aFileDescriptors); + + params.optionalStream() = wrappedParams; + } + else { + params.optionalStream() = mozilla::void_t(); + } + + params.bufferSize() = mBufferSize; + + aParams = params; +} + +bool +nsBufferedInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const BufferedInputStreamParams& params = + aParams.get_BufferedInputStreamParams(); + const OptionalInputStreamParams& wrappedParams = params.optionalStream(); + + nsCOMPtr stream; + if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) { + stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(), + aFileDescriptors); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + } + else { + NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t, + "Unknown type for OptionalInputStreamParams!"); + } + + nsresult rv = Init(stream, params.bufferSize()); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +Maybe +nsBufferedInputStream::ExpectedSerializedLength() +{ + nsCOMPtr stream = do_QueryInterface(mStream); + if (stream) { + return stream->ExpectedSerializedLength(); + } + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedOutputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream) +// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for +// non-nullness of mSafeStream. +NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIOutputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +nsresult +nsBufferedOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsBufferedOutputStream* stream = new nsBufferedOutputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) +{ + // QI stream to an nsISafeOutputStream, to see if we should support it + mSafeStream = do_QueryInterface(stream); + + return nsBufferedStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsBufferedOutputStream::Close() +{ + nsresult rv1, rv2 = NS_OK, rv3; + rv1 = Flush(); + // If we fail to Flush all the data, then we close anyway and drop the + // remaining data in the buffer. We do this because it's what Unix does + // for fclose and close. However, we report the error from Flush anyway. + if (mStream) { + rv2 = Sink()->Close(); + NS_RELEASE(mStream); + } + rv3 = nsBufferedStream::Close(); + if (NS_FAILED(rv1)) return rv1; + if (NS_FAILED(rv2)) return rv2; + return rv3; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Write(const char *buf, uint32_t count, uint32_t *result) +{ + nsresult rv = NS_OK; + uint32_t written = 0; + while (count > 0) { + uint32_t amt = std::min(count, mBufferSize - mCursor); + if (amt > 0) { + memcpy(mBuffer + mCursor, buf + written, amt); + written += amt; + count -= amt; + mCursor += amt; + if (mFillPoint < mCursor) + mFillPoint = mCursor; + } + else { + NS_ASSERTION(mFillPoint, "iloop in nsBufferedOutputStream::Write!"); + rv = Flush(); + if (NS_FAILED(rv)) break; + } + } + *result = written; + return (written > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Flush() +{ + nsresult rv; + uint32_t amt; + if (!mStream) { + // Stream already cancelled/flushed; probably because of previous error. + return NS_OK; + } + rv = Sink()->Write(mBuffer, mFillPoint, &amt); + if (NS_FAILED(rv)) return rv; + mBufferStartOffset += amt; + if (amt == mFillPoint) { + mFillPoint = mCursor = 0; + return NS_OK; // flushed everything + } + + // slide the remainder down to the start of the buffer + // |<-------------->|<---|----->| + // b a c s + uint32_t rem = mFillPoint - amt; + memmove(mBuffer, mBuffer + amt, rem); + mFillPoint = mCursor = rem; + return NS_ERROR_FAILURE; // didn't flush all +} + +// nsISafeOutputStream +NS_IMETHODIMP +nsBufferedOutputStream::Finish() +{ + // flush the stream, to write out any buffered data... + nsresult rv = nsBufferedOutputStream::Flush(); + if (NS_FAILED(rv)) + NS_WARNING("failed to flush buffered data! possible dataloss"); + + // ... and finish the underlying stream... + if (NS_SUCCEEDED(rv)) + rv = mSafeStream->Finish(); + else + Sink()->Close(); + + // ... and close the buffered stream, so any further attempts to flush/close + // the buffered stream won't cause errors. + nsBufferedStream::Close(); + + return rv; +} + +static nsresult +nsReadFromInputStream(nsIOutputStream* outStr, + void* closure, + char* toRawSegment, + uint32_t offset, + uint32_t count, + uint32_t *readCount) +{ + nsIInputStream* fromStream = (nsIInputStream*)closure; + return fromStream->Read(toRawSegment, count, readCount); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + return WriteSegments(nsReadFromInputStream, inStr, count, _retval); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + *_retval = 0; + nsresult rv; + while (count > 0) { + uint32_t left = std::min(count, mBufferSize - mCursor); + if (left == 0) { + rv = Flush(); + if (NS_FAILED(rv)) + return (*_retval > 0) ? NS_OK : rv; + + continue; + } + + uint32_t read = 0; + rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read); + + if (NS_FAILED(rv)) // If we have written some data, return ok + return (*_retval > 0) ? NS_OK : rv; + mCursor += read; + *_retval += read; + count -= read; + mFillPoint = std::max(mFillPoint, mCursor); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::IsNonBlocking(bool *aNonBlocking) +{ + if (mStream) + return Sink()->IsNonBlocking(aNonBlocking); + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP_(char*) +nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) + return nullptr; + + if (mBufferDisabled) + return nullptr; + + char* buf = mBuffer + mCursor; + uint32_t rem = mBufferSize - mCursor; + if (rem == 0) { + if (NS_FAILED(Flush())) + return nullptr; + buf = mBuffer + mCursor; + rem = mBufferSize - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) + return nullptr; + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) + return nullptr; + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) + return; + + NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch"); + mCursor += aLength; + if (mFillPoint < mCursor) + mFillPoint = mCursor; +} + +NS_IMETHODIMP +nsBufferedOutputStream::DisableBuffering() +{ + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) + return NS_ERROR_UNEXPECTED; + + // Empty the buffer so nsBufferedStream::Tell works. + nsresult rv = Flush(); + if (NS_FAILED(rv)) + return rv; + + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::EnableBuffering() +{ + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::GetUnbufferedStream(nsISupports* *aStream) +{ + // Empty the buffer so subsequent i/o trumps any buffered data. + if (mFillPoint) { + nsresult rv = Flush(); + if (NS_FAILED(rv)) + return rv; + } + + *aStream = mStream; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +#undef METER + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h new file mode 100644 index 000000000..93a770beb --- /dev/null +++ b/netwerk/base/nsBufferedStreams.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBufferedStreams_h__ +#define nsBufferedStreams_h__ + +#include "nsIBufferedStreams.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsIStreamBufferAccess.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedStream : public nsISeekableStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + + nsBufferedStream(); + + nsresult Close(); + +protected: + virtual ~nsBufferedStream(); + + nsresult Init(nsISupports* stream, uint32_t bufferSize); + NS_IMETHOD Fill() = 0; + NS_IMETHOD Flush() = 0; + + uint32_t mBufferSize; + char* mBuffer; + + // mBufferStartOffset is the offset relative to the start of mStream. + int64_t mBufferStartOffset; + + // mCursor is the read cursor for input streams, or write cursor for + // output streams, and is relative to mBufferStartOffset. + uint32_t mCursor; + + // mFillPoint is the amount available in the buffer for input streams, + // or the high watermark of bytes written into the buffer, and therefore + // is relative to mBufferStartOffset. + uint32_t mFillPoint; + + nsISupports* mStream; // cast to appropriate subclass + + bool mBufferDisabled; + bool mEOF; // True if mStream is at EOF + uint8_t mGetBufferCount; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedInputStream : public nsBufferedStream, + public nsIBufferedInputStream, + public nsIStreamBufferAccess, + public nsIIPCSerializableInputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsBufferedInputStream() : nsBufferedStream() {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + nsIInputStream* Source() { + return (nsIInputStream*)mStream; + } + +protected: + virtual ~nsBufferedInputStream() {} + + NS_IMETHOD Fill() override; + NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedOutputStream final : public nsBufferedStream, + public nsISafeOutputStream, + public nsIBufferedOutputStream, + public nsIStreamBufferAccess +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSISAFEOUTPUTSTREAM + NS_DECL_NSIBUFFEREDOUTPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + + nsBufferedOutputStream() : nsBufferedStream() {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + nsIOutputStream* Sink() { + return (nsIOutputStream*)mStream; + } + +protected: + virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); } + + NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams + + nsCOMPtr mSafeStream; // QI'd from mStream +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsBufferedStreams_h__ diff --git a/netwerk/base/nsChannelClassifier.cpp b/netwerk/base/nsChannelClassifier.cpp new file mode 100644 index 000000000..6b9f9ede3 --- /dev/null +++ b/netwerk/base/nsChannelClassifier.cpp @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsChannelClassifier.h" + +#include "mozIThirdPartyUtil.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsICacheEntry.h" +#include "nsICachingChannel.h" +#include "nsIChannel.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIOService.h" +#include "nsIParentChannel.h" +#include "nsIPermissionManager.h" +#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsISecureBrowserUI.h" +#include "nsISecurityEventSink.h" +#include "nsIURL.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsXULAppAPI.h" + +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" + +namespace mozilla { +namespace net { + +// +// MOZ_LOG=nsChannelClassifier:5 +// +static LazyLogModule gChannelClassifierLog("nsChannelClassifier"); + +#undef LOG +#define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Debug) + +NS_IMPL_ISUPPORTS(nsChannelClassifier, + nsIURIClassifierCallback) + +nsChannelClassifier::nsChannelClassifier() + : mIsAllowListed(false), + mSuspendedChannel(false) +{ +} + +nsresult +nsChannelClassifier::ShouldEnableTrackingProtection(nsIChannel *aChannel, + bool *result) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + NS_ENSURE_ARG(result); + *result = false; + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + if (!loadContext || !(loadContext->UseTrackingProtection())) { + return NS_OK; + } + + nsresult rv; + nsCOMPtr thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr chan = do_QueryInterface(aChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr topWinURI; + rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!topWinURI) { + LOG(("nsChannelClassifier[%p]: No window URI\n", this)); + } + + nsCOMPtr chanURI; + rv = aChannel->GetURI(getter_AddRefs(chanURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // Third party checks don't work for chrome:// URIs in mochitests, so just + // default to isThirdParty = true. We check isThirdPartyWindow to expand + // the list of domains that are considered first party (e.g., if + // facebook.com includes an iframe from fatratgames.com, all subsources + // included in that iframe are considered third-party with + // isThirdPartyChannel, even if they are not third-party w.r.t. + // facebook.com), and isThirdPartyChannel to prevent top-level navigations + // from being detected as third-party. + bool isThirdPartyChannel = true; + bool isThirdPartyWindow = true; + thirdPartyUtil->IsThirdPartyURI(chanURI, topWinURI, &isThirdPartyWindow); + thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &isThirdPartyChannel); + if (!isThirdPartyWindow || !isThirdPartyChannel) { + *result = false; + if (LOG_ENABLED()) { + LOG(("nsChannelClassifier[%p]: Skipping tracking protection checks " + "for first party or top-level load channel[%p] with uri %s", + this, aChannel, chanURI->GetSpecOrDefault().get())); + } + return NS_OK; + } + + nsCOMPtr ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + const char ALLOWLIST_EXAMPLE_PREF[] = "channelclassifier.allowlist_example"; + if (!topWinURI && Preferences::GetBool(ALLOWLIST_EXAMPLE_PREF, false)) { + LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this)); + rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"), + nullptr, nullptr, getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Take the host/port portion so we can allowlist by site. Also ignore the + // scheme, since users who put sites on the allowlist probably don't expect + // allowlisting to depend on scheme. + nsCOMPtr url = do_QueryInterface(topWinURI, &rv); + if (NS_FAILED(rv)) { + return rv; // normal for some loads, no need to print a warning + } + + nsCString escaped(NS_LITERAL_CSTRING("https://")); + nsAutoCString temp; + rv = url->GetHostPort(temp); + NS_ENSURE_SUCCESS(rv, rv); + escaped.Append(temp); + + // Stuff the whole thing back into a URI for the permission manager. + rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr permMgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; + rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions); + NS_ENSURE_SUCCESS(rv, rv); + + if (permissions == nsIPermissionManager::ALLOW_ACTION) { + LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] for %s", this, + aChannel, escaped.get())); + mIsAllowListed = true; + *result = false; + } else { + *result = true; + } + + // In Private Browsing Mode we also check against an in-memory list. + if (NS_UsePrivateBrowsing(aChannel)) { + nsCOMPtr pbmtpWhitelist = + do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + mIsAllowListed = true; + LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] in PBM for %s", + this, aChannel, escaped.get())); + } + + *result = !exists; + } + + // Tracking protection will be enabled so return without updating + // the security state. If any channels are subsequently cancelled + // (page elements blocked) the state will be then updated. + if (*result) { + if (LOG_ENABLED()) { + LOG(("nsChannelClassifier[%p]: Enabling tracking protection checks on " + "channel[%p] with uri %s for toplevel window %s", this, aChannel, + chanURI->GetSpecOrDefault().get(), + topWinURI->GetSpecOrDefault().get())); + } + return NS_OK; + } + + // Tracking protection will be disabled so update the security state + // of the document and fire a secure change event. If we can't get the + // window for the channel, then the shield won't show up so we can't send + // an event to the securityUI anyway. + return NotifyTrackingProtectionDisabled(aChannel); +} + +// static +nsresult +nsChannelClassifier::NotifyTrackingProtectionDisabled(nsIChannel *aChannel) +{ + // Can be called in EITHER the parent or child process. + nsCOMPtr parentChannel; + NS_QueryNotificationCallbacks(aChannel, parentChannel); + if (parentChannel) { + // This channel is a parent-process proxy for a child process request. + // Tell the child process channel to do this instead. + parentChannel->NotifyTrackingProtectionDisabled(); + return NS_OK; + } + + nsresult rv; + nsCOMPtr thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr win; + rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, rv); + + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + nsCOMPtr doc = docShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_OK); + + // Notify nsIWebProgressListeners of this security event. + // Can be used to change the UI state. + nsCOMPtr eventSink = do_QueryInterface(docShell, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + uint32_t state = 0; + nsCOMPtr securityUI; + docShell->GetSecurityUI(getter_AddRefs(securityUI)); + if (!securityUI) { + return NS_OK; + } + doc->SetHasTrackingContentLoaded(true); + securityUI->GetState(&state); + state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT; + eventSink->OnSecurityChange(nullptr, state); + + return NS_OK; +} + +void +nsChannelClassifier::Start(nsIChannel *aChannel) +{ + mChannel = aChannel; + + nsresult rv = StartInternal(); + if (NS_FAILED(rv)) { + // If we aren't getting a callback for any reason, assume a good verdict and + // make sure we resume the channel if necessary. + OnClassifyComplete(NS_OK); + } +} + +nsresult +nsChannelClassifier::StartInternal() +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't bother to run the classifier on a load that has already failed. + // (this might happen after a redirect) + nsresult status; + mChannel->GetStatus(&status); + if (NS_FAILED(status)) + return status; + + // Don't bother to run the classifier on a cached load that was + // previously classified as good. + if (HasBeenClassified(mChannel)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr uri; + nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't bother checking certain types of URIs. + bool hasFlags; + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_DANGEROUS_TO_LOAD, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_FILE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + // Skip whitelisted hostnames. + nsAutoCString whitelisted; + Preferences::GetCString("urlclassifier.skipHostnames", &whitelisted); + if (!whitelisted.IsEmpty()) { + ToLowerCase(whitelisted); + LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s", + this, whitelisted.get())); + if (IsHostnameWhitelisted(uri, whitelisted)) { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || + rv == NS_ERROR_NOT_AVAILABLE) { + // no URI classifier, ignore this failure. + return NS_ERROR_NOT_AVAILABLE; + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal; + rv = securityManager->GetChannelURIPrincipal(mChannel, getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + bool expectCallback; + bool trackingProtectionEnabled = false; + (void)ShouldEnableTrackingProtection(mChannel, &trackingProtectionEnabled); + + if (LOG_ENABLED()) { + nsCOMPtr principalURI; + principal->GetURI(getter_AddRefs(principalURI)); + LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel with " + "uri %s", this, principalURI->GetSpecOrDefault().get(), + uri->GetSpecOrDefault().get())); + } + rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this, + &expectCallback); + if (NS_FAILED(rv)) { + return rv; + } + + if (expectCallback) { + // Suspend the channel, it will be resumed when we get the classifier + // callback. + rv = mChannel->Suspend(); + if (NS_FAILED(rv)) { + // Some channels (including nsJSChannel) fail on Suspend. This + // shouldn't be fatal, but will prevent malware from being + // blocked on these channels. + LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this)); + return rv; + } + + mSuspendedChannel = true; + LOG(("nsChannelClassifier[%p]: suspended channel %p", + this, mChannel.get())); + } else { + LOG(("nsChannelClassifier[%p]: not expecting callback", this)); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +nsChannelClassifier::IsHostnameWhitelisted(nsIURI *aUri, + const nsACString &aWhitelisted) +{ + nsAutoCString host; + nsresult rv = aUri->GetHost(host); + if (NS_FAILED(rv) || host.IsEmpty()) { + return false; + } + ToLowerCase(host); + + nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ','); + while (tokenizer.hasMoreTokens()) { + const nsCSubstring& token = tokenizer.nextToken(); + if (token.Equals(host)) { + LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)", + this, host.get())); + return true; + } + } + + return false; +} + +// Note in the cache entry that this URL was classified, so that future +// cached loads don't need to be checked. +void +nsChannelClassifier::MarkEntryClassified(nsresult status) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't cache tracking classifications because we support allowlisting. + if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) { + return; + } + + if (LOG_ENABLED()) { + nsAutoCString errorName; + GetErrorName(status, errorName); + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s", + errorName.get(), spec.get())); + } + + nsCOMPtr cachingChannel = do_QueryInterface(mChannel); + if (!cachingChannel) { + return; + } + + nsCOMPtr cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return; + } + + nsCOMPtr cacheEntry = + do_QueryInterface(cacheToken); + if (!cacheEntry) { + return; + } + + cacheEntry->SetMetaDataElement("necko:classified", + NS_SUCCEEDED(status) ? "1" : nullptr); +} + +bool +nsChannelClassifier::HasBeenClassified(nsIChannel *aChannel) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr cachingChannel = + do_QueryInterface(aChannel); + if (!cachingChannel) { + return false; + } + + // Only check the tag if we are loading from the cache without + // validation. + bool fromCache; + if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) { + return false; + } + + nsCOMPtr cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return false; + } + + nsCOMPtr cacheEntry = + do_QueryInterface(cacheToken); + if (!cacheEntry) { + return false; + } + + nsXPIDLCString tag; + cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag)); + return tag.EqualsLiteral("1"); +} + +//static +bool +nsChannelClassifier::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel) +{ + nsCOMPtr docURI = aDoc->GetDocumentURI(); + nsCOMPtr channelLoadInfo = aChannel->GetLoadInfo(); + if (!channelLoadInfo || !docURI) { + return false; + } + + nsCOMPtr channelLoadingPrincipal = channelLoadInfo->LoadingPrincipal(); + if (!channelLoadingPrincipal) { + // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top level + // loads should not be blocked by Tracking Protection, so we will return + // false + return false; + } + nsCOMPtr channelLoadingURI; + channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI)); + if (!channelLoadingURI) { + return false; + } + bool equals = false; + nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals); + return NS_SUCCEEDED(rv) && equals; +} + +// static +nsresult +nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel) +{ + // Can be called in EITHER the parent or child process. + nsCOMPtr parentChannel; + NS_QueryNotificationCallbacks(channel, parentChannel); + if (parentChannel) { + // This channel is a parent-process proxy for a child process request. The + // actual channel will be notified via the status passed to + // nsIRequest::Cancel and do this for us. + return NS_OK; + } + + nsresult rv; + nsCOMPtr win; + nsCOMPtr thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, NS_OK); + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + nsCOMPtr doc = docShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_OK); + + // This event might come after the user has navigated to another page. + // To prevent showing the TrackingProtection UI on the wrong page, we need to + // check that the loading URI for the channel is the same as the URI currently + // loaded in the document. + if (!SameLoadingURI(doc, channel)) { + return NS_OK; + } + + // Notify nsIWebProgressListeners of this security event. + // Can be used to change the UI state. + nsCOMPtr eventSink = do_QueryInterface(docShell, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + uint32_t state = 0; + nsCOMPtr securityUI; + docShell->GetSecurityUI(getter_AddRefs(securityUI)); + if (!securityUI) { + return NS_OK; + } + doc->SetHasTrackingContentBlocked(true); + securityUI->GetState(&state); + state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT; + eventSink->OnSecurityChange(nullptr, state); + + // Log a warning to the web console. + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault()); + const char16_t* params[] = { spec.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Tracking Protection"), + doc, + nsContentUtils::eNECKO_PROPERTIES, + "TrackingUriBlocked", + params, ArrayLength(params)); + + return NS_OK; +} + +nsresult +nsChannelClassifier::IsTrackerWhitelisted() +{ + nsresult rv; + nsCOMPtr uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tables; + Preferences::GetCString("urlclassifier.trackingWhitelistTable", &tables); + + if (tables.IsEmpty()) { + LOG(("nsChannelClassifier[%p]:IsTrackerWhitelisted whitelist disabled", + this)); + return NS_ERROR_TRACKING_URI; + } + + nsCOMPtr chan = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr topWinURI; + rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + if (!topWinURI) { + LOG(("nsChannelClassifier[%p]: No window URI", this)); + return NS_ERROR_TRACKING_URI; + } + + nsCOMPtr securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr chanPrincipal; + rv = securityManager->GetChannelURIPrincipal(mChannel, + getter_AddRefs(chanPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + // Craft a whitelist URL like "toplevel.page/?resource=third.party.domain" + nsAutoCString pageHostname, resourceDomain; + rv = topWinURI->GetHost(pageHostname); + NS_ENSURE_SUCCESS(rv, rv); + rv = chanPrincipal->GetBaseDomain(resourceDomain); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString whitelistEntry = NS_LITERAL_CSTRING("http://") + + pageHostname + NS_LITERAL_CSTRING("/?resource=") + resourceDomain; + LOG(("nsChannelClassifier[%p]: Looking for %s in the whitelist", + this, whitelistEntry.get())); + + nsCOMPtr whitelistURI; + rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry); + NS_ENSURE_SUCCESS(rv, rv); + + // Check whether or not the tracker is in the entity whitelist + nsAutoCString results; + rv = uriClassifier->ClassifyLocalWithTables(whitelistURI, tables, results); + NS_ENSURE_SUCCESS(rv, rv); + if (!results.IsEmpty()) { + return NS_OK; // found it on the whitelist, must not be blocked + } + + LOG(("nsChannelClassifier[%p]: %s is not in the whitelist", + this, whitelistEntry.get())); + return NS_ERROR_TRACKING_URI; +} + +NS_IMETHODIMP +nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + if (aErrorCode == NS_ERROR_TRACKING_URI && + NS_SUCCEEDED(IsTrackerWhitelisted())) { + LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found " + "in whitelist so we won't block it", this)); + aErrorCode = NS_OK; + } + + if (mSuspendedChannel) { + nsAutoCString errorName; + if (LOG_ENABLED()) { + GetErrorName(aErrorCode, errorName); + LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)", + this, errorName.get())); + } + MarkEntryClassified(aErrorCode); + + if (NS_FAILED(aErrorCode)) { + if (LOG_ENABLED()) { + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s " + "with error code %s", this, mChannel.get(), + uri->GetSpecOrDefault().get(), errorName.get())); + } + + // Channel will be cancelled (page element blocked) due to tracking. + // Do update the security state of the document and fire a security + // change event. + if (aErrorCode == NS_ERROR_TRACKING_URI) { + SetBlockedTrackingContent(mChannel); + } + + mChannel->Cancel(aErrorCode); + } + LOG(("nsChannelClassifier[%p]: resuming channel %p from " + "OnClassifyComplete", this, mChannel.get())); + mChannel->Resume(); + } + + mChannel = nullptr; + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsChannelClassifier.h b/netwerk/base/nsChannelClassifier.h new file mode 100644 index 000000000..20575f3c1 --- /dev/null +++ b/netwerk/base/nsChannelClassifier.h @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsChannelClassifier_h__ +#define nsChannelClassifier_h__ + +#include "nsIURIClassifier.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIChannel; +class nsIHttpChannelInternal; +class nsIDocument; + +namespace mozilla { +namespace net { + +class nsChannelClassifier final : public nsIURIClassifierCallback +{ +public: + nsChannelClassifier(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIURICLASSIFIERCALLBACK + + // Calls nsIURIClassifier.Classify with the principal of the given channel, + // and cancels the channel on a bad verdict. + void Start(nsIChannel *aChannel); + // Whether or not tracking protection should be enabled on this channel. + nsresult ShouldEnableTrackingProtection(nsIChannel *aChannel, bool *result); + +private: + // True if the channel is on the allow list. + bool mIsAllowListed; + // True if the channel has been suspended. + bool mSuspendedChannel; + nsCOMPtr mChannel; + + ~nsChannelClassifier() {} + // Caches good classifications for the channel principal. + void MarkEntryClassified(nsresult status); + bool HasBeenClassified(nsIChannel *aChannel); + // Helper function so that we ensure we call ContinueBeginConnect once + // Start is called. Returns NS_OK if and only if we will get a callback + // from the classifier service. + nsresult StartInternal(); + // Helper function to check a tracking URI against the whitelist + nsresult IsTrackerWhitelisted(); + // Helper function to check a URI against the hostname whitelist + bool IsHostnameWhitelisted(nsIURI *aUri, const nsACString &aWhitelisted); + // Checks that the channel was loaded by the URI currently loaded in aDoc + static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel); + +public: + // If we are blocking tracking content, update the corresponding flag in + // the respective docshell and call nsISecurityEventSink::onSecurityChange. + static nsresult SetBlockedTrackingContent(nsIChannel *channel); + static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel); +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp new file mode 100644 index 000000000..e09315ed1 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.cpp @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDNSPrefetch.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsICancelable.h" +#include "nsIURI.h" + +static nsIDNSService *sDNSService = nullptr; + +nsresult +nsDNSPrefetch::Initialize(nsIDNSService *aDNSService) +{ + NS_IF_RELEASE(sDNSService); + sDNSService = aDNSService; + NS_IF_ADDREF(sDNSService); + return NS_OK; +} + +nsresult +nsDNSPrefetch::Shutdown() +{ + NS_IF_RELEASE(sDNSService); + return NS_OK; +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI *aURI, + nsIDNSListener *aListener, + bool storeTiming) + : mStoreTiming(storeTiming) + , mListener(do_GetWeakReference(aListener)) +{ + aURI->GetAsciiHost(mHostname); +} + +nsresult +nsDNSPrefetch::Prefetch(uint16_t flags) +{ + if (mHostname.IsEmpty()) + return NS_ERROR_NOT_AVAILABLE; + + if (!sDNSService) + return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr 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 mainThread = do_GetMainThread(); + return sDNSService->AsyncResolve(mHostname, + flags | nsIDNSService::RESOLVE_SPECULATE, + this, mainThread, + getter_AddRefs(tmpOutstanding)); +} + +nsresult +nsDNSPrefetch::PrefetchLow(bool refreshDNS) +{ + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | + (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0)); +} + +nsresult +nsDNSPrefetch::PrefetchMedium(bool refreshDNS) +{ + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0)); +} + +nsresult +nsDNSPrefetch::PrefetchHigh(bool refreshDNS) +{ + return Prefetch(refreshDNS ? + nsIDNSService::RESOLVE_BYPASS_CACHE : 0); +} + + +NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener) + +NS_IMETHODIMP +nsDNSPrefetch::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread."); + + if (mStoreTiming) { + mEndTimestamp = mozilla::TimeStamp::Now(); + } + nsCOMPtr listener = do_QueryReferent(mListener); + if (listener) { + listener->OnLookupComplete(request, rec, status); + } + return NS_OK; +} diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h new file mode 100644 index 000000000..3ad6d4bf0 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDNSPrefetch_h___ +#define nsDNSPrefetch_h___ + +#include "nsWeakReference.h" +#include "nsString.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" + +#include "nsIDNSListener.h" + +class nsIURI; +class nsIDNSService; + +class nsDNSPrefetch final : public nsIDNSListener +{ + ~nsDNSPrefetch() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + nsDNSPrefetch(nsIURI *aURI, nsIDNSListener *aListener, bool storeTiming); + bool TimingsValid() const { + return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull(); + } + // Only use the two timings if TimingsValid() returns true + const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; } + const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; } + + static nsresult Initialize(nsIDNSService *aDNSService); + static nsresult Shutdown(); + + // Call one of the following methods to start the Prefetch. + nsresult PrefetchHigh(bool refreshDNS = false); + nsresult PrefetchMedium(bool refreshDNS = false); + nsresult PrefetchLow(bool refreshDNS = false); + +private: + nsCString mHostname; + bool mStoreTiming; + mozilla::TimeStamp mStartTimestamp; + mozilla::TimeStamp mEndTimestamp; + nsWeakPtr mListener; + + nsresult Prefetch(uint16_t flags); +}; + +#endif diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp new file mode 100644 index 000000000..87a57fd57 --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.cpp @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set sw=4 sts=4 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/* + + The converts a filesystem directory into an "HTTP index" stream per + Lou Montulli's original spec: + + http://www.mozilla.org/projects/netlib/dirindexformat.html + + */ + +#include "nsEscape.h" +#include "nsDirectoryIndexStream.h" +#include "mozilla/Logging.h" +#include "prtime.h" +#include "nsISimpleEnumerator.h" +#ifdef THREADSAFE_I18N +#include "nsCollationCID.h" +#include "nsICollation.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#endif +#include "nsIFile.h" +#include "nsURLHelper.h" +#include "nsNativeCharsetUtils.h" + +// NOTE: This runs on the _file transport_ thread. +// The problem is that now that we're actually doing something with the data, +// we want to do stuff like i18n sorting. However, none of the collation stuff +// is threadsafe. +// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current +// behaviour, though. See bug 99382. +// When this is fixed, #define THREADSAFE_I18N to get this code working + +//#define THREADSAFE_I18N + +using namespace mozilla; +static LazyLogModule gLog("nsDirectoryIndexStream"); + +nsDirectoryIndexStream::nsDirectoryIndexStream() + : mOffset(0), mStatus(NS_OK), mPos(0) +{ + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: created", this)); +} + +static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData) +{ + if (!NS_IsNativeUTF8()) { + // don't check for errors, because we can't report them anyway + nsAutoString name1, name2; + aElement1->GetLeafName(name1); + aElement2->GetLeafName(name2); + + // Note - we should do the collation to do sorting. Why don't we? + // Because that is _slow_. Using TestProtocols to list file:///dev/ + // goes from 3 seconds to 22. (This may be why nsXULSortService is + // so slow as well). + // Does this have bad effects? Probably, but since nsXULTree appears + // to use the raw RDF literal value as the sort key (which ammounts to an + // strcmp), it won't be any worse, I think. + // This could be made faster, by creating the keys once, + // but CompareString could still be smarter - see bug 99383 - bbaetz + // NB - 99393 has been WONTFIXed. So if the I18N code is ever made + // threadsafe so that this matters, we'd have to pass through a + // struct { nsIFile*, uint8_t* } with the pre-calculated key. + return Compare(name1, name2); + } + + nsAutoCString name1, name2; + aElement1->GetNativeLeafName(name1); + aElement2->GetNativeLeafName(name2); + + return Compare(name1, name2); +} + +nsresult +nsDirectoryIndexStream::Init(nsIFile* aDir) +{ + nsresult rv; + bool isDir; + rv = aDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + NS_PRECONDITION(isDir, "not a directory"); + if (!isDir) + return NS_ERROR_ILLEGAL_VALUE; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + nsAutoCString path; + aDir->GetNativePath(path); + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: initialized on %s", + this, path.get())); + } + + // Sigh. We have to allocate on the heap because there are no + // assignment operators defined. + nsCOMPtr iter; + rv = aDir->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) return rv; + + // Now lets sort, because clients expect it that way + // XXX - should we do so here, or when the first item is requested? + // XXX - use insertion sort instead? + + bool more; + nsCOMPtr elem; + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr file = do_QueryInterface(elem); + if (file) + mArray.AppendObject(file); // addrefs + } + } + +#ifdef THREADSAFE_I18N + nsCOMPtr ls = do_GetService(NS_LOCALESERVICE_CONTRACTID, + &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr locale; + rv = ls->GetApplicationLocale(getter_AddRefs(locale)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, + &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr coll; + rv = cf->CreateCollation(locale, getter_AddRefs(coll)); + if (NS_FAILED(rv)) return rv; + + mArray.Sort(compare, coll); +#else + mArray.Sort(compare, nullptr); +#endif + + mBuf.AppendLiteral("300: "); + nsAutoCString url; + rv = net_GetURLSpecFromFile(aDir, url); + if (NS_FAILED(rv)) return rv; + mBuf.Append(url); + mBuf.Append('\n'); + + mBuf.AppendLiteral("200: filename content-length last-modified file-type\n"); + + return NS_OK; +} + +nsDirectoryIndexStream::~nsDirectoryIndexStream() +{ + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: destroyed", this)); +} + +nsresult +nsDirectoryIndexStream::Create(nsIFile* aDir, nsIInputStream** aResult) +{ + RefPtr result = new nsDirectoryIndexStream(); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = result->Init(aDir); + if (NS_FAILED(rv)) { + return rv; + } + + result.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream) + +// The below routines are proxied to the UI thread! +NS_IMETHODIMP +nsDirectoryIndexStream::Close() +{ + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::Available(uint64_t* aLength) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + // If there's data in our buffer, use that + if (mOffset < (int32_t)mBuf.Length()) { + *aLength = mBuf.Length() - mOffset; + return NS_OK; + } + + // Returning one byte is not ideal, but good enough + *aLength = (mPos < mArray.Count()) ? 1 : 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount) +{ + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aReadCount = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) + return mStatus; + + uint32_t nread = 0; + + // If anything is enqueued (or left-over) in mBuf, then feed it to + // the reader first. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + + // Room left? + if (aCount > 0) { + mOffset = 0; + mBuf.Truncate(); + + // Okay, now we'll suck stuff off of our iterator into the mBuf... + while (uint32_t(mBuf.Length()) < aCount) { + bool more = mPos < mArray.Count(); + if (!more) break; + + // don't addref, for speed - an addref happened when it + // was placed in the array, so it's not going to go stale + nsIFile* current = mArray.ObjectAt(mPos); + ++mPos; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + nsAutoCString path; + current->GetNativePath(path); + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: iterated %s", + this, path.get())); + } + + // rjc: don't return hidden files/directories! + // bbaetz: why not? + nsresult rv; +#ifndef XP_UNIX + bool hidden = false; + current->IsHidden(&hidden); + if (hidden) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: skipping hidden file/directory", + this)); + continue; + } +#endif + + int64_t fileSize = 0; + current->GetFileSize( &fileSize ); + + PRTime fileInfoModifyTime = 0; + current->GetLastModifiedTime( &fileInfoModifyTime ); + fileInfoModifyTime *= PR_USEC_PER_MSEC; + + mBuf.AppendLiteral("201: "); + + // The "filename" field + if (!NS_IsNativeUTF8()) { + nsAutoString leafname; + rv = current->GetLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && + NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } else { + nsAutoCString leafname; + rv = current->GetNativeLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && + NS_Escape(leafname, escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } + + // The "content-length" field + mBuf.AppendInt(fileSize, 10); + mBuf.Append(' '); + + // The "last-modified" field + PRExplodedTime tm; + PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm); + { + char buf[64]; + PR_FormatTimeUSEnglish(buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); + mBuf.Append(buf); + } + + // The "file-type" field + bool isFile = true; + current->IsFile(&isFile); + if (isFile) { + mBuf.AppendLiteral("FILE "); + } + else { + bool isDir; + rv = current->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + if (isDir) { + mBuf.AppendLiteral("DIRECTORY "); + } + else { + bool isLink; + rv = current->IsSymlink(&isLink); + if (NS_FAILED(rv)) return rv; + if (isLink) { + mBuf.AppendLiteral("SYMBOLIC-LINK "); + } + } + } + + mBuf.Append('\n'); + } + + // ...and once we've either run out of directory entries, or + // filled up the buffer, then we'll push it to the reader. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + } + + *aReadCount = nread; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h new file mode 100644 index 000000000..5403c7af2 --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDirectoryIndexStream_h__ +#define nsDirectoryIndexStream_h__ + +#include "mozilla/Attributes.h" + +#include "nsString.h" +#include "nsIInputStream.h" +#include "nsCOMArray.h" + +class nsIFile; + +class nsDirectoryIndexStream final : public nsIInputStream +{ +private: + nsCString mBuf; + int32_t mOffset; + nsresult mStatus; + + int32_t mPos; // position within mArray + nsCOMArray mArray; // file objects within the directory + + nsDirectoryIndexStream(); + /** + * aDir will only be used on the calling thread. + */ + nsresult Init(nsIFile* aDir); + ~nsDirectoryIndexStream(); + +public: + /** + * aDir will only be used on the calling thread. + */ + static nsresult + Create(nsIFile* aDir, nsIInputStream** aStreamResult); + + // nsISupportsInterface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIInputStream interface + NS_DECL_NSIINPUTSTREAM +}; + +#endif // nsDirectoryIndexStream_h__ + diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp new file mode 100644 index 000000000..6248be8f1 --- /dev/null +++ b/netwerk/base/nsDownloader.cpp @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDownloader.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsNetUtil.h" +#include "nsCRTGlue.h" + +nsDownloader::~nsDownloader() +{ + if (mLocation && mLocationIsTemp) { + // release the sink first since it may still hold an open file + // descriptor to mLocation. this needs to happen before the + // file can be removed otherwise the Remove call will fail. + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + nsresult rv = mLocation->Remove(false); + if (NS_FAILED(rv)) + NS_ERROR("unable to remove temp file"); + } +} + +NS_IMPL_ISUPPORTS(nsDownloader, + nsIDownloader, + nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +nsDownloader::Init(nsIDownloadObserver *observer, nsIFile *location) +{ + mObserver = observer; + mLocation = location; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + nsresult rv; + if (!mLocation) { + nsCOMPtr location; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location)); + if (NS_FAILED(rv)) return rv; + + char buf[13]; + NS_MakeRandomString(buf, 8); + memcpy(buf+8, ".tmp", 5); + rv = location->AppendNative(nsDependentCString(buf, 12)); + if (NS_FAILED(rv)) return rv; + + rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) return rv; + + location.swap(mLocation); + mLocationIsTemp = true; + } + + rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation); + if (NS_FAILED(rv)) return rv; + + // we could wrap this output stream with a buffered output stream, + // but it shouldn't be necessary since we will be writing large + // chunks given to us via OnDataAvailable. + + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStopRequest(nsIRequest *request, + nsISupports *ctxt, + nsresult status) +{ + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + mObserver->OnDownloadComplete(this, request, ctxt, status, mLocation); + mObserver = nullptr; + + return NS_OK; +} + +nsresult +nsDownloader::ConsumeData(nsIInputStream* in, + void* closure, + const char* fromRawSegment, + uint32_t toOffset, + uint32_t count, + uint32_t *writeCount) +{ + nsDownloader *self = (nsDownloader *) closure; + if (self->mSink) + return self->mSink->Write(fromRawSegment, count, writeCount); + + *writeCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + uint32_t n; + return inStr->ReadSegments(ConsumeData, this, count, &n); +} diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h new file mode 100644 index 000000000..b5c22393d --- /dev/null +++ b/netwerk/base/nsDownloader.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDownloader_h__ +#define nsDownloader_h__ + +#include "nsIDownloader.h" +#include "nsCOMPtr.h" + +class nsIFile; +class nsIOutputStream; + +class nsDownloader : public nsIDownloader +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOWNLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsDownloader() : mLocationIsTemp(false) {} + +protected: + virtual ~nsDownloader(); + + static nsresult ConsumeData(nsIInputStream *in, + void *closure, + const char *fromRawSegment, + uint32_t toOffset, + uint32_t count, + uint32_t *writeCount); + + nsCOMPtr mObserver; + nsCOMPtr mLocation; + nsCOMPtr mSink; + bool mLocationIsTemp; +}; + +#endif // nsDownloader_h__ diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp new file mode 100644 index 000000000..2ddb7ae98 --- /dev/null +++ b/netwerk/base/nsFileStreams.cpp @@ -0,0 +1,1153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ipc/IPCMessageUtils.h" + +#if defined(XP_UNIX) || defined(XP_BEOS) +#include +#elif defined(XP_WIN) +#include +#include "nsILocalFileWin.h" +#else +// XXX add necessary include file for ftruncate (or equivalent) +#endif + +#include "private/pprio.h" + +#include "nsFileStreams.h" +#include "nsIFile.h" +#include "nsReadLine.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/FileUtils.h" +#include "nsNetCID.h" +#include "nsXULAppAPI.h" + +#define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067 + +typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType; + +using namespace mozilla::ipc; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStreamBase + +nsFileStreamBase::nsFileStreamBase() + : mFD(nullptr) + , mBehaviorFlags(0) + , mDeferredOpen(false) +{ +} + +nsFileStreamBase::~nsFileStreamBase() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsFileStreamBase, + nsISeekableStream, + nsIFileMetadata) + +NS_IMETHODIMP +nsFileStreamBase::Seek(int32_t whence, int64_t offset) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::Tell(int64_t *result) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::SetEOF() +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + +#if defined(XP_UNIX) || defined(XP_BEOS) + // Some system calls require an EOF offset. + int64_t offset; + rv = Tell(&offset); + if (NS_FAILED(rv)) return rv; +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) + if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(mFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } +#else + // XXX not implemented +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetSize(int64_t* _retval) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + *_retval = int64_t(info.size); + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetLastModified(int64_t* _retval) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *_retval = 0; + } + else { + *_retval = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) +{ + nsresult rv = DoPendingOpen(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mFD; + return NS_OK; +} + +nsresult +nsFileStreamBase::Close() +{ + CleanUpOpen(); + + nsresult rv = NS_OK; + if (mFD) { + if (PR_Close(mFD) == PR_FAILURE) + rv = NS_BASE_STREAM_OSERROR; + mFD = nullptr; + } + return rv; +} + +nsresult +nsFileStreamBase::Available(uint64_t* aResult) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + // PR_Available with files over 4GB returns an error, so we have to + // use the 64-bit version of PR_Available. + int64_t avail = PR_Available64(mFD); + if (avail == -1) { + return NS_ErrorAccordingToNSPR(); + } + + // If available is greater than 4GB, return 4GB + *aResult = (uint64_t)avail; + return NS_OK; +} + +nsresult +nsFileStreamBase::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + nsresult rv = DoPendingOpen(); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + // Don't warn if this is just a deferred file not found. + return rv; + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + *aResult = 0; + return NS_OK; + } + + int32_t bytesRead = PR_Read(mFD, aBuf, aCount); + if (bytesRead == -1) { + return NS_ErrorAccordingToNSPR(); + } + + *aResult = bytesRead; + return NS_OK; +} + +nsresult +nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + // ReadSegments is not implemented because it would be inefficient when + // the writer does not consume all data. If you want to call ReadSegments, + // wrap a BufferedInputStream around the file stream. That will call + // Read(). + + // If this is ever implemented you might need to modify + // nsPartialFileInputStream::ReadSegments + + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFileStreamBase::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +nsresult +nsFileStreamBase::Flush(void) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + int32_t cnt = PR_Sync(mFD); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +nsresult +nsFileStreamBase::Write(const char *buf, uint32_t count, uint32_t *result) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + int32_t cnt = PR_Write(mFD, buf, count); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +nsresult +nsFileStreamBase::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteFrom (see source comment)"); + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult +nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult +nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags, + int32_t aPerm, bool aDeferred) +{ + NS_ENSURE_STATE(aFile); + + mOpenParams.ioFlags = aIoFlags; + mOpenParams.perm = aPerm; + + if (aDeferred) { + // Clone the file, as it may change between now and the deferred open + nsCOMPtr file; + nsresult rv = aFile->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + mOpenParams.localFile = do_QueryInterface(file); + NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED); + + mDeferredOpen = true; + return NS_OK; + } + + mOpenParams.localFile = aFile; + + // Following call open() at main thread. + // Main thread might be blocked, while open a remote file. + return DoOpen(); +} + +void +nsFileStreamBase::CleanUpOpen() +{ + mOpenParams.localFile = nullptr; + mDeferredOpen = false; +} + +nsresult +nsFileStreamBase::DoOpen() +{ + NS_ASSERTION(!mFD, "Already have a file descriptor!"); + NS_ASSERTION(mOpenParams.localFile, "Must have a file to open"); + + PRFileDesc* fd; + nsresult rv; + +#ifdef XP_WIN + if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) { + nsCOMPtr file = do_QueryInterface(mOpenParams.localFile); + MOZ_ASSERT(file); + + rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags, + mOpenParams.perm, + &fd); + } else +#endif // XP_WIN + { + rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags, + mOpenParams.perm, + &fd); + } + + CleanUpOpen(); + if (NS_FAILED(rv)) + return rv; + mFD = fd; + + return NS_OK; +} + +nsresult +nsFileStreamBase::DoPendingOpen() +{ + if (!mDeferredOpen) { + return NS_OK; + } + + return DoOpen(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileInputStream + +NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase) +NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase) + +NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_LOCALFILEINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsILineInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsFileInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, + nsIInputStream, + nsIFileInputStream, + nsISeekableStream, + nsILineInputStream) + +nsresult +nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsFileInputStream* stream = new nsFileInputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +nsresult +nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm) +{ + nsresult rv = NS_OK; + + // If the previous file is open, close it + if (mFD) { + rv = Close(); + if (NS_FAILED(rv)) return rv; + } + + // Open the file + if (aIOFlags == -1) + aIOFlags = PR_RDONLY; + if (aPerm == -1) + aPerm = 0; + + rv = MaybeOpen(aFile, aIOFlags, aPerm, + mBehaviorFlags & nsIFileInputStream::DEFER_OPEN); + + if (NS_FAILED(rv)) return rv; + + // if defer open is set, do not remove the file here. + // remove the file while Close() is called. + if ((mBehaviorFlags & DELETE_ON_CLOSE) && + !(mBehaviorFlags & nsIFileInputStream::DEFER_OPEN)) { + // POSIX compatible filesystems allow a file to be unlinked while a + // file descriptor is still referencing the file. since we've already + // opened the file descriptor, we'll try to remove the file. if that + // fails, then we'll just remember the nsIFile and remove it after we + // close the file descriptor. + rv = aFile->Remove(false); + if (NS_SUCCEEDED(rv)) { + // No need to remove it later. Clear the flag. + mBehaviorFlags &= ~DELETE_ON_CLOSE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) +{ + NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = aBehaviorFlags; + + mFile = aFile; + mIOFlags = aIOFlags; + mPerm = aPerm; + + return Open(aFile, aIOFlags, aPerm); +} + +NS_IMETHODIMP +nsFileInputStream::Close() +{ + // Get the cache position at the time the file was close. This allows + // NS_SEEK_CUR on a closed file that has been opened with + // REOPEN_ON_REWIND. + if (mBehaviorFlags & REOPEN_ON_REWIND) { + // Get actual position. Not one modified by subclasses + nsFileStreamBase::Tell(&mCachedPosition); + } + + // null out mLineBuffer in case Close() is called again after failing + mLineBuffer = nullptr; + nsresult rv = nsFileStreamBase::Close(); + if (NS_FAILED(rv)) return rv; + if (mFile && (mBehaviorFlags & DELETE_ON_CLOSE)) { + rv = mFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to delete file"); + // If we don't need to save the file for reopening, free it up + if (!(mBehaviorFlags & REOPEN_ON_REWIND)) { + mFile = nullptr; + } + } + return rv; +} + +NS_IMETHODIMP +nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + // Don't warn if this is a deffered file not found. + return rv; + } + + NS_ENSURE_SUCCESS(rv, rv); + + // Check if we're at the end of file and need to close + if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) { + Close(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) +{ + if (!mLineBuffer) { + mLineBuffer = new nsLineBuffer; + } + return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + return SeekInternal(aWhence, aOffset); +} + +nsresult +nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (aClearBuf) { + mLineBuffer = nullptr; + } + if (!mFD) { + if (mBehaviorFlags & REOPEN_ON_REWIND) { + rv = Open(mFile, mIOFlags, mPerm); + NS_ENSURE_SUCCESS(rv, rv); + + // If the file was closed, and we do a relative seek, use the + // position we cached when we closed the file to seek to the right + // location. + if (aWhence == NS_SEEK_CUR) { + aWhence = NS_SEEK_SET; + aOffset += mCachedPosition; + } + } else { + return NS_BASE_STREAM_CLOSED; + } + } + + return nsFileStreamBase::Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +nsFileInputStream::Tell(int64_t *aResult) +{ + return nsFileStreamBase::Tell(aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Available(uint64_t *aResult) +{ + return nsFileStreamBase::Available(aResult); +} + +void +nsFileInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + FileInputStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen()) && mFD) { + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + NS_ASSERTION(fd, "This should never be null!"); + + DebugOnly dbgFD = aFileDescriptors.AppendElement(fd); + NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!"); + + params.fileDescriptorIndex() = aFileDescriptors.Length() - 1; + + Close(); + } else { + NS_WARNING("This file has not been opened (or could not be opened). " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptorIndex() = UINT32_MAX; + } + + int32_t behaviorFlags = mBehaviorFlags; + + // The receiving process (or thread) is going to have an open file + // descriptor automatically so transferring this flag is meaningless. + behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN; + + params.behaviorFlags() = behaviorFlags; + params.ioFlags() = mIOFlags; + + aParams = params; +} + +bool +nsFileInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(!mDeferredOpen, "Deferring open?!"); + NS_ASSERTION(!mFile, "Should never have a file here!"); + NS_ASSERTION(!mPerm, "This should always be 0!"); + + if (aParams.type() != InputStreamParams::TFileInputStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const FileInputStreamParams& params = aParams.get_FileInputStreamParams(); + + uint32_t fileDescriptorIndex = params.fileDescriptorIndex(); + + FileDescriptor fd; + if (fileDescriptorIndex < aFileDescriptors.Length()) { + fd = aFileDescriptors[fileDescriptorIndex]; + NS_WARNING_ASSERTION(fd.IsValid(), + "Received an invalid file descriptor!"); + } else { + NS_WARNING("Received a bad file descriptor index!"); + } + + if (fd.IsValid()) { + auto rawFD = fd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return false; + } + mFD = fileDesc; + } + + mBehaviorFlags = params.behaviorFlags(); + + if (!XRE_IsParentProcess()) { + // A child process shouldn't close when it reads the end because it will + // not be able to reopen the file later. + mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF; + + // A child process will not be able to reopen the file so this flag is + // meaningless. + mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND; + } + + mIOFlags = params.ioFlags(); + + return true; +} + +Maybe +nsFileInputStream::ExpectedSerializedLength() +{ + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsPartialFileInputStream + +NS_IMPL_ADDREF_INHERITED(nsPartialFileInputStream, nsFileStreamBase) +NS_IMPL_RELEASE_INHERITED(nsPartialFileInputStream, nsFileStreamBase) + +NS_IMPL_CLASSINFO(nsPartialFileInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_PARTIALLOCALFILEINPUTSTREAM_CID) + +// Don't forward to nsFileInputStream as we don't want to QI to +// nsIFileInputStream +NS_INTERFACE_MAP_BEGIN(nsPartialFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIPartialFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsILineInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsPartialFileInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsPartialFileInputStream, + nsIInputStream, + nsIPartialFileInputStream, + nsISeekableStream, + nsILineInputStream) + +nsresult +nsPartialFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsPartialFileInputStream* stream = new nsPartialFileInputStream(); + + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Init(nsIFile* aFile, uint64_t aStart, + uint64_t aLength, int32_t aIOFlags, + int32_t aPerm, int32_t aBehaviorFlags) +{ + mStart = aStart; + mLength = aLength; + mPosition = 0; + + nsresult rv = nsFileInputStream::Init(aFile, aIOFlags, aPerm, + aBehaviorFlags); + + // aFile is a partial file, it must exist. + NS_ENSURE_SUCCESS(rv, rv); + + mDeferredSeek = true; + + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Tell(int64_t *aResult) +{ + int64_t tell = 0; + + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsFileInputStream::Tell(&tell); + NS_ENSURE_SUCCESS(rv, rv); + + + *aResult = tell - mStart; + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Available(uint64_t* aResult) +{ + uint64_t available = 0; + + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsFileInputStream::Available(&available); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = TruncateSize(available); + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t readsize = (uint32_t) TruncateSize(aCount); + if (readsize == 0 && mBehaviorFlags & CLOSE_ON_EOF) { + Close(); + *aResult = 0; + return NS_OK; + } + + rv = nsFileInputStream::Read(aBuf, readsize, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + mPosition += readsize; + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t offset; + switch (aWhence) { + case NS_SEEK_SET: + offset = mStart + aOffset; + break; + case NS_SEEK_CUR: + offset = mStart + mPosition + aOffset; + break; + case NS_SEEK_END: + offset = mStart + mLength + aOffset; + break; + default: + return NS_ERROR_ILLEGAL_VALUE; + } + + if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) { + return NS_ERROR_INVALID_ARG; + } + + rv = nsFileInputStream::Seek(NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, rv); + + mPosition = offset - mStart; + return rv; +} + +void +nsPartialFileInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + // Serialize the base class first. + InputStreamParams fileParams; + nsFileInputStream::Serialize(fileParams, aFileDescriptors); + + PartialFileInputStreamParams params; + + params.fileStreamParams() = fileParams.get_FileInputStreamParams(); + params.begin() = mStart; + params.length() = mLength; + + aParams = params; +} + +bool +nsPartialFileInputStream::Deserialize( + const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(!mStart, "Already have a start?!"); + NS_ASSERTION(!mLength, "Already have a length?!"); + NS_ASSERTION(!mPosition, "Already have a position?!"); + + if (aParams.type() != InputStreamParams::TPartialFileInputStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const PartialFileInputStreamParams& params = + aParams.get_PartialFileInputStreamParams(); + + // Deserialize the base class first. + InputStreamParams fileParams(params.fileStreamParams()); + if (!nsFileInputStream::Deserialize(fileParams, aFileDescriptors)) { + NS_WARNING("Base class deserialize failed!"); + return false; + } + + NS_ASSERTION(mFD, "Must have a file descriptor now!"); + + mStart = params.begin(); + mLength = params.length(); + mPosition = 0; + + if (!mStart) { + return true; + } + + // XXX This is so broken. Main thread IO alert. + return NS_SUCCEEDED(nsFileInputStream::Seek(NS_SEEK_SET, mStart)); +} + +Maybe +nsPartialFileInputStream::ExpectedSerializedLength() +{ + return Some(mLength); +} + + +nsresult +nsPartialFileInputStream::DoPendingSeek() +{ + if (!mDeferredSeek) { + return NS_OK; + } + + mDeferredSeek = false; + + // This is the first time to open the file, don't clear mLinebuffer. + // mLineBuffer might be already initialized by ReadLine(). + return nsFileInputStream::SeekInternal(NS_SEEK_SET, mStart, false); +} +//////////////////////////////////////////////////////////////////////////////// +// nsFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, + nsFileStreamBase, + nsIOutputStream, + nsIFileOutputStream) + +nsresult +nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsFileOutputStream* stream = new nsFileOutputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) +{ + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + + if (ioFlags == -1) + ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + if (perm <= 0) + perm = 0664; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN); +} + +NS_IMETHODIMP +nsFileOutputStream::Preallocate(int64_t aLength) +{ + if (!mFD) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!mozilla::fallocate(mFD, aLength)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAtomicFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, + nsFileOutputStream, + nsISafeOutputStream, + nsIOutputStream, + nsIFileOutputStream) + +NS_IMETHODIMP +nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) +{ + // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter + // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending + // to existing file. So, throw an exception only if `PR_APPEND` is + // explicitly specified without `PR_TRUNCATE`. + if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) { + return NS_ERROR_INVALID_ARG; + } + return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags); +} + +nsresult +nsAtomicFileOutputStream::DoOpen() +{ + // Make sure mOpenParams.localFile will be empty if we bail somewhere in + // this function + nsCOMPtr 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)) { + tempResult->SetFollowLinks(true); + + // XP_UNIX ignores SetFollowLinks(), so we have to normalize. + if (mTargetFileExists) { + tempResult->Normalize(); + } + } + + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + uint32_t origPerm; + if (NS_FAILED(file->GetPermissions(&origPerm))) { + NS_ERROR("Can't get permissions of target file"); + origPerm = mOpenParams.perm; + } + // XXX What if |perm| is more restrictive then |origPerm|? + // This leaves the user supplied permissions as they were. + rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm); + } + if (NS_SUCCEEDED(rv)) { + // nsFileOutputStream::DoOpen will work on the temporary file, so we + // prepare it and place it in mOpenParams.localFile. + mOpenParams.localFile = tempResult; + mTempFile = tempResult; + mTargetFile = file; + rv = nsFileOutputStream::DoOpen(); + } + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Close() +{ + nsresult rv = nsFileOutputStream::Close(); + + // the consumer doesn't want the original file overwritten - + // so clean up by removing the temp file. + if (mTempFile) { + mTempFile->Remove(false); + mTempFile = nullptr; + } + + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Finish() +{ + nsresult rv = nsFileOutputStream::Close(); + + // if there is no temp file, don't try to move it over the original target. + // It would destroy the targetfile if close() is called twice. + if (!mTempFile) + return rv; + + // Only overwrite if everything was ok, and the temp file could be closed. + if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) { + NS_ENSURE_STATE(mTargetFile); + + if (!mTargetFileExists) { + // If the target file did not exist when we were initialized, then the + // temp file we gave out was actually a reference to the target file. + // since we succeeded in writing to the temp file (and hence succeeded + // in writing to the target file), there is nothing more to do. +#ifdef DEBUG + bool equal; + if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) + NS_WARNING("mTempFile not equal to mTargetFile"); +#endif + } + else { + nsAutoString targetFilename; + rv = mTargetFile->GetLeafName(targetFilename); + if (NS_SUCCEEDED(rv)) { + // This will replace target. + rv = mTempFile->MoveTo(nullptr, targetFilename); + if (NS_FAILED(rv)) + mTempFile->Remove(false); + } + } + } + else { + mTempFile->Remove(false); + + // if writing failed, propagate the failure code to the caller. + if (NS_FAILED(mWriteResult)) + rv = mWriteResult; + } + mTempFile = nullptr; + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Write(const char *buf, uint32_t count, uint32_t *result) +{ + nsresult rv = nsFileOutputStream::Write(buf, count, result); + if (NS_SUCCEEDED(mWriteResult)) { + if (NS_FAILED(rv)) + mWriteResult = rv; + else if (count != *result) + mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + + if (NS_FAILED(mWriteResult) && count > 0) + NS_WARNING("writing to output stream failed! data may be lost"); + } + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsSafeFileOutputStream + +NS_IMETHODIMP +nsSafeFileOutputStream::Finish() +{ + (void) Flush(); + return nsAtomicFileOutputStream::Finish(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileStream, + nsFileStreamBase, + nsIInputStream, + nsIOutputStream, + nsIFileStream) + +NS_IMETHODIMP +nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) +{ + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + + if (ioFlags == -1) + ioFlags = PR_RDWR; + if (perm <= 0) + perm = 0; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileStream::DEFER_OPEN); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h new file mode 100644 index 000000000..22ef91770 --- /dev/null +++ b/netwerk/base/nsFileStreams.h @@ -0,0 +1,333 @@ +// /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFileStreams_h__ +#define nsFileStreams_h__ + +#include "nsAutoPtr.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsILineInputStream.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsReadLine.h" +#include + + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileStreamBase : public nsISeekableStream, + public nsIFileMetadata +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIFILEMETADATA + + nsFileStreamBase(); + +protected: + virtual ~nsFileStreamBase(); + + nsresult Close(); + nsresult Available(uint64_t* _retval); + nsresult Read(char* aBuf, uint32_t aCount, uint32_t* _retval); + nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval); + nsresult IsNonBlocking(bool* _retval); + nsresult Flush(); + nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* _retval); + nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval); + nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval); + + PRFileDesc* mFD; + + /** + * Flags describing our behavior. See the IDL file for possible values. + */ + int32_t mBehaviorFlags; + + /** + * Whether we have a pending open (see DEFER_OPEN in the IDL file). + */ + bool mDeferredOpen; + + struct OpenParams { + nsCOMPtr localFile; + int32_t ioFlags; + int32_t perm; + }; + + /** + * Data we need to do an open. + */ + OpenParams mOpenParams; + + /** + * Prepares the data we need to open the file, and either does the open now + * by calling DoOpen(), or leaves it to be opened later by a call to + * DoPendingOpen(). + */ + nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm, + bool aDeferred); + + /** + * Cleans up data prepared in MaybeOpen. + */ + void CleanUpOpen(); + + /** + * Open the file. This is called either from MaybeOpen (during Init) + * or from DoPendingOpen (if DEFER_OPEN is used when initializing this + * stream). The default behavior of DoOpen is to open the file and save the + * file descriptor. + */ + virtual nsresult DoOpen(); + + /** + * If there is a pending open, do it now. It's important for this to be + * inline since we do it in almost every stream API call. + */ + inline nsresult DoPendingOpen(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileInputStream : public nsFileStreamBase, + public nsIFileInputStream, + public nsILineInputStream, + public nsIIPCSerializableInputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEINPUTSTREAM + NS_DECL_NSILINEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + NS_IMETHOD Close() override; + NS_IMETHOD Tell(int64_t *aResult) override; + NS_IMETHOD Available(uint64_t* _retval) override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, + uint32_t aCount, uint32_t* _retval) override + { + return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, + _retval); + } + NS_IMETHOD IsNonBlocking(bool* _retval) override + { + return nsFileStreamBase::IsNonBlocking(_retval); + } + + // Overrided from nsFileStreamBase + NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override; + + nsFileInputStream() + : mLineBuffer(nullptr), mIOFlags(0), mPerm(0), mCachedPosition(0) + {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsFileInputStream() + { + Close(); + } + + nsresult SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf=true); + + nsAutoPtr > mLineBuffer; + + /** + * The file being opened. + */ + nsCOMPtr mFile; + /** + * The IO flags passed to Init() for the file open. + */ + int32_t mIOFlags; + /** + * The permissions passed to Init() for the file open. + */ + int32_t mPerm; + + /** + * Cached position for Tell for automatically reopening streams. + */ + int64_t mCachedPosition; + +protected: + /** + * Internal, called to open a file. Parameters are the same as their + * Init() analogues. + */ + nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsPartialFileInputStream : public nsFileInputStream, + public nsIPartialFileInputStream +{ +public: + using nsFileInputStream::Init; + using nsFileInputStream::Read; + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPARTIALFILEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsPartialFileInputStream() + : mStart(0), mLength(0), mPosition(0), mDeferredSeek(false) + { } + + NS_IMETHOD Tell(int64_t *aResult) override; + NS_IMETHOD Available(uint64_t *aResult) override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* aResult) override; + NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override; + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + ~nsPartialFileInputStream() + { } + + inline nsresult DoPendingSeek(); + +private: + uint64_t TruncateSize(uint64_t aSize) { + return std::min(mLength - mPosition, aSize); + } + + uint64_t mStart; + uint64_t mLength; + uint64_t mPosition; + bool mDeferredSeek; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileOutputStream : public nsFileStreamBase, + public nsIFileOutputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEOUTPUTSTREAM + NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::) + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsFileOutputStream() + { + Close(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing is complete. This protects against incomplete writes + * due to the process or the thread being interrupted or crashed. + */ +class nsAtomicFileOutputStream : public nsFileOutputStream, + public nsISafeOutputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISAFEOUTPUTSTREAM + + nsAtomicFileOutputStream() : + mTargetFileExists(true), + mWriteResult(NS_OK) {} + + virtual nsresult DoOpen() override; + + NS_IMETHOD Close() override; + NS_IMETHOD Write(const char *buf, uint32_t count, uint32_t *result) override; + NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) override; + +protected: + virtual ~nsAtomicFileOutputStream() + { + Close(); + } + + nsCOMPtr mTargetFile; + nsCOMPtr mTempFile; + + bool mTargetFileExists; + nsresult mWriteResult; // Internally set in Write() + +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing + flushing is complete. This protects against more + * classes of software/hardware errors than nsAtomicFileOutputStream, + * at the expense of being more costly to the disk, OS and battery. + */ +class nsSafeFileOutputStream : public nsAtomicFileOutputStream +{ +public: + + NS_IMETHOD Finish() override; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileStream : public nsFileStreamBase, + public nsIInputStream, + public nsIOutputStream, + public nsIFileStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILESTREAM + NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::) + + // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods + // Close() and IsNonBlocking() + NS_IMETHOD Flush() override + { + return nsFileStreamBase::Flush(); + } + NS_IMETHOD Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) override + { + return nsFileStreamBase::Write(aBuf, aCount, _retval); + } + NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) override + { + return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval); + } + NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) override + { + return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, + _retval); + } + +protected: + virtual ~nsFileStream() + { + Close(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsFileStreams_h__ diff --git a/netwerk/base/nsIApplicationCache.idl b/netwerk/base/nsIApplicationCache.idl new file mode 100644 index 000000000..9922feb59 --- /dev/null +++ b/netwerk/base/nsIApplicationCache.idl @@ -0,0 +1,205 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIArray; +interface nsIFile; +interface nsIURI; + +/** + * Application caches can store a set of namespace entries that affect + * loads from the application cache. If a load from the cache fails + * to match an exact cache entry, namespaces entries will be searched + * for a substring match, and should be applied appropriately. + */ +[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)] +interface nsIApplicationCacheNamespace : nsISupports +{ + /** + * Items matching this namespace can be fetched from the network + * when loading from this cache. The "data" attribute is unused. + */ + const unsigned long NAMESPACE_BYPASS = 1 << 0; + + /** + * Items matching this namespace can be fetched from the network + * when loading from this cache. If the load fails, the cache entry + * specified by the "data" attribute should be loaded instead. + */ + const unsigned long NAMESPACE_FALLBACK = 1 << 1; + + /** + * Items matching this namespace should be cached + * opportunistically. Successful toplevel loads of documents + * in this namespace should be placed in the application cache. + * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify + * NAMESPACE_FALLBACK to supply a fallback entry. + */ + const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2; + + /** + * Initialize the namespace. + */ + void init(in unsigned long itemType, + in ACString namespaceSpec, + in ACString data); + + /** + * The namespace type. + */ + readonly attribute unsigned long itemType; + + /** + * The prefix of this namespace. This should be the asciiSpec of the + * URI prefix. + */ + readonly attribute ACString namespaceSpec; + + /** + * Data associated with this namespace, such as a fallback. URI data should + * use the asciiSpec of the URI. + */ + readonly attribute ACString data; +}; + +/** + * Application caches store resources for offline use. Each + * application cache has a unique client ID for use with + * nsICacheService::openSession() to access the cache's entries. + * + * Each entry in the application cache can be marked with a set of + * types, as discussed in the WHAT-WG offline applications + * specification. + * + * All application caches with the same group ID belong to a cache + * group. Each group has one "active" cache that will service future + * loads. Inactive caches will be removed from the cache when they are + * no longer referenced. + */ +[scriptable, uuid(06568DAE-C374-4383-A122-0CC96C7177F2)] +interface nsIApplicationCache : nsISupports +{ + /** + * Init this application cache instance to just hold the group ID and + * the client ID to work just as a handle to the real cache. Used on + * content process to simplify the application cache code. + */ + void initAsHandle(in ACString groupId, in ACString clientId); + + /** + * Entries in an application cache can be marked as one or more of + * the following types. + */ + + /* This item is the application manifest. */ + const unsigned long ITEM_MANIFEST = 1 << 0; + + /* This item was explicitly listed in the application manifest. */ + const unsigned long ITEM_EXPLICIT = 1 << 1; + + /* This item was navigated in a toplevel browsing context, and + * named this cache's group as its manifest. */ + const unsigned long ITEM_IMPLICIT = 1 << 2; + + /* This item was added by the dynamic scripting API */ + const unsigned long ITEM_DYNAMIC = 1 << 3; + + /* This item was listed in the application manifest, but named a + * different cache group as its manifest. */ + const unsigned long ITEM_FOREIGN = 1 << 4; + + /* This item was listed as a fallback entry. */ + const unsigned long ITEM_FALLBACK = 1 << 5; + + /* This item matched an opportunistic cache namespace and was + * cached accordingly. */ + const unsigned long ITEM_OPPORTUNISTIC = 1 << 6; + + /** + * URI of the manfiest specifying this application cache. + **/ + readonly attribute nsIURI manifestURI; + + /** + * The group ID for this cache group. It is an internally generated string + * and cannot be used as manifest URL spec. + **/ + readonly attribute ACString groupID; + + /** + * The client ID for this application cache. Clients can open a + * session with nsICacheService::createSession() using this client + * ID and a storage policy of STORE_OFFLINE to access this cache. + */ + readonly attribute ACString clientID; + + /** + * TRUE if the cache is the active cache for this group. + */ + readonly attribute boolean active; + + /** + * The disk usage of the application cache, in bytes. + */ + readonly attribute unsigned long usage; + + /** + * Makes this cache the active application cache for this group. + * Future loads associated with this group will come from this + * cache. Other caches from this cache group will be deactivated. + */ + void activate(); + + /** + * Discard this application cache. Removes all cached resources + * for this cache. If this is the active application cache for the + * group, the group will be removed. + */ + void discard(); + + /** + * Adds item types to a given entry. + */ + void markEntry(in ACString key, in unsigned long typeBits); + + /** + * Removes types from a given entry. If the resulting entry has + * no types left, the entry is removed. + */ + void unmarkEntry(in ACString key, in unsigned long typeBits); + + /** + * Gets the types for a given entry. + */ + unsigned long getTypes(in ACString key); + + /** + * Returns any entries in the application cache whose type matches + * one or more of the bits in typeBits. + */ + void gatherEntries(in uint32_t typeBits, + out unsigned long count, + [array, size_is(count)] out string keys); + + /** + * Add a set of namespace entries to the application cache. + * @param namespaces + * An nsIArray of nsIApplicationCacheNamespace entries. + */ + void addNamespaces(in nsIArray namespaces); + + /** + * Get the most specific namespace matching a given key. + */ + nsIApplicationCacheNamespace getMatchingNamespace(in ACString key); + + /** + * If set, this offline cache is placed in a different directory + * than the current application profile. + */ + readonly attribute nsIFile profileDirectory; +}; diff --git a/netwerk/base/nsIApplicationCacheChannel.idl b/netwerk/base/nsIApplicationCacheChannel.idl new file mode 100644 index 000000000..410e2946d --- /dev/null +++ b/netwerk/base/nsIApplicationCacheChannel.idl @@ -0,0 +1,57 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIApplicationCacheContainer.idl" + +/** + * Interface implemented by channels that support application caches. + */ +[scriptable, uuid(6FA816B1-6D5F-4380-9704-054D0908CFA3)] +interface nsIApplicationCacheChannel : nsIApplicationCacheContainer +{ + /** + * TRUE when the resource came from the application cache. This + * might be false even there is assigned an application cache + * e.g. in case of fallback of load of an entry matching bypass + * namespace. + */ + readonly attribute boolean loadedFromApplicationCache; + + /** + * When true, the channel will ask its notification callbacks for + * an application cache if one is not explicitly provided. Default + * value is true. + * + * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen() + * is called. + */ + attribute boolean inheritApplicationCache; + + /** + * When true, the channel will choose an application cache if one + * was not explicitly provided and none is available from the + * notification callbacks. Default value is false. + * + * This attribute will not be transferred through a redirect. + * + * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen() + * is called. + */ + attribute boolean chooseApplicationCache; + + /** + * A shortcut method to mark the cache item of this channel as 'foreign'. + * See the 'cache selection algorithm' and CACHE_SELECTION_RELOAD + * action handling in nsContentSink. + */ + void markOfflineCacheEntryAsForeign(); + + /** + * Set offline application cache object to instruct the channel + * to cache for offline use using this application cache. + */ + attribute nsIApplicationCache applicationCacheForWrite; +}; diff --git a/netwerk/base/nsIApplicationCacheContainer.idl b/netwerk/base/nsIApplicationCacheContainer.idl new file mode 100644 index 000000000..af0b74cbe --- /dev/null +++ b/netwerk/base/nsIApplicationCacheContainer.idl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIApplicationCache; + +/** + * Interface used by objects that can be associated with an + * application cache. + */ +[scriptable, uuid(bbb80700-1f7f-4258-aff4-1743cc5a7d23)] +interface nsIApplicationCacheContainer : nsISupports +{ + attribute nsIApplicationCache applicationCache; +}; diff --git a/netwerk/base/nsIApplicationCacheService.idl b/netwerk/base/nsIApplicationCacheService.idl new file mode 100644 index 000000000..9b2b16955 --- /dev/null +++ b/netwerk/base/nsIApplicationCacheService.idl @@ -0,0 +1,110 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIApplicationCache; +interface nsIFile; +interface nsIURI; +interface nsILoadContextInfo; + +/** + * The application cache service manages the set of application cache + * groups. + */ +[scriptable, uuid(b8b6546c-6cec-4bda-82df-08e006a97b56)] +interface nsIApplicationCacheService : nsISupports +{ + /** + * Create group string identifying cache group according the manifest + * URL and the given principal. + */ + ACString buildGroupIDForInfo(in nsIURI aManifestURL, + in nsILoadContextInfo aLoadContextInfo); + ACString buildGroupIDForSuffix(in nsIURI aManifestURL, + in ACString aOriginSuffix); + + /** + * Create a new, empty application cache for the given cache + * group. + */ + nsIApplicationCache createApplicationCache(in ACString group); + + /** + * Create a new, empty application cache for the given cache + * group residing in a custom directory with a custom quota. + * + * @param group + * URL of the manifest + * @param directory + * Actually a reference to a profile directory where to + * create the OfflineCache sub-dir. + * @param quota + * Optional override of the default quota. + */ + nsIApplicationCache createCustomApplicationCache(in ACString group, + in nsIFile profileDir, + in int32_t quota); + + /** + * Get an application cache object for the given client ID. + */ + nsIApplicationCache getApplicationCache(in ACString clientID); + + /** + * Get the currently active cache object for a cache group. + */ + nsIApplicationCache getActiveCache(in ACString group); + + /** + * Deactivate the currently-active cache object for a cache group. + */ + void deactivateGroup(in ACString group); + + /** + * Evict offline cache entries, either all of them or those belonging + * to the given origin. + */ + void evict(in nsILoadContextInfo aLoadContextInfo); + + /** + * Delete caches whom origin attributes matches the given pattern. + */ + void evictMatchingOriginAttributes(in AString aPattern); + + /** + * Try to find the best application cache to serve a resource. + */ + nsIApplicationCache chooseApplicationCache(in ACString key, + [optional] in nsILoadContextInfo aLoadContextInfo); + + /** + * Flags the key as being opportunistically cached. + * + * This method should also propagate the entry to other + * application caches with the same opportunistic namespace, but + * this is not currently implemented. + * + * @param cache + * The cache in which the entry is cached now. + * @param key + * The cache entry key. + */ + void cacheOpportunistically(in nsIApplicationCache cache, in ACString key); + + /** + * Get the list of application cache groups. + */ + void getGroups([optional] out unsigned long count, + [array, size_is(count), retval] out string groupIDs); + + /** + * Get the list of application cache groups in the order of + * activating time. + */ + void getGroupsTimeOrdered([optional] out unsigned long count, + [array, size_is(count), retval] out string groupIDs); +}; diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl new file mode 100644 index 000000000..430f63b2e --- /dev/null +++ b/netwerk/base/nsIArrayBufferInputStream.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" + +/** + * nsIArrayBufferInputStream + * + * Provides scriptable methods for initializing a nsIInputStream + * implementation with an ArrayBuffer. + */ +[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)] +interface nsIArrayBufferInputStream : nsIInputStream +{ + /** + * SetData - assign an ArrayBuffer to the input stream. + * + * @param buffer - stream data + * @param byteOffset - stream data offset + * @param byteLen - stream data length + */ + [implicit_jscontext] + void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl new file mode 100644 index 000000000..633fe72b6 --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +// You should prefer nsIAsyncStreamCopier2 +[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)] +interface nsIAsyncStreamCopier : nsIRequest +{ + /** + * Initialize the stream copier. + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aSourceBuffered + * true if aSource implements ReadSegments. + * @param aSinkBuffered + * true if aSink implements WriteSegments. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying. + * @param aCloseSink + * true if aSink should be closed after copying. + * + * NOTE: at least one of the streams must be buffered. If you do not know + * whether your streams are buffered, you should use nsIAsyncStreamCopier2 + * instead. + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in boolean aSourceBuffered, + in boolean aSinkBuffered, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl new file mode 100644 index 000000000..7de793f51 --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier2.idl @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)] +interface nsIAsyncStreamCopier2 : nsIRequest +{ + /** + * Initialize the stream copier. + * + * If neither the source nor the sink are buffered, buffering will + * be automatically added to the sink. + * + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying (this is generally + * the desired behavior). + * @param aCloseSink + * true if aSink should be closed after copying (this is generally + * the desired behavior). + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl new file mode 100644 index 000000000..8c81a142f --- /dev/null +++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)] +interface nsIAsyncVerifyRedirectCallback : nsISupports +{ + /** + * Complement to nsIChannelEventSink asynchronous callback. The result of + * the redirect decision is passed through this callback. + * + * @param result + * Result of the redirect veto decision. If FAILED the redirect has been + * vetoed. If SUCCEEDED the redirect has been allowed by all consumers. + */ + void onRedirectVerifyCallback(in nsresult result); +}; diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl new file mode 100644 index 000000000..484d59a8c --- /dev/null +++ b/netwerk/base/nsIAuthInformation.idl @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A object that hold authentication information. The caller of + * nsIAuthPrompt2::promptUsernameAndPassword or + * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this + * interface; the prompt implementation can then read the values here to prefill + * the dialog. After the user entered the authentication information, it should + * set the attributes of this object to indicate to the caller what was entered + * by the user. + */ +[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)] +interface nsIAuthInformation : nsISupports +{ + /** @name Flags */ + /* @{ */ + /** + * This dialog belongs to a network host. + */ + const uint32_t AUTH_HOST = 1; + + /** + * This dialog belongs to a proxy. + */ + const uint32_t AUTH_PROXY = 2; + + /** + * This dialog needs domain information. The user interface should show a + * domain field, prefilled with the domain attribute's value. + */ + const uint32_t NEED_DOMAIN = 4; + + /** + * This dialog only asks for password information. Authentication prompts + * SHOULD NOT show a username field. Attempts to change the username field + * will have no effect. nsIAuthPrompt2 implementations should, however, show + * its initial value to the user in some form. For example, a paragraph in + * the dialog might say "Please enter your password for user jsmith at + * server intranet". + * + * This flag is mutually exclusive with #NEED_DOMAIN. + */ + const uint32_t ONLY_PASSWORD = 8; + + /** + * We have already tried to log in for this channel + * (with auth values from a previous promptAuth call), + * but it failed, so we now ask the user to provide a new, correct login. + * + * @see also RFC 2616, Section 10.4.2 + */ + const uint32_t PREVIOUS_FAILED = 16; + + /** + * A cross-origin sub-resource requests an authentication. + * The message presented to users must reflect that. + */ + const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32; + /* @} */ + + /** + * Flags describing this dialog. A bitwise OR of the flag values + * above. + * + * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set. + * + * Auth prompts should ignore flags they don't understand; especially, they + * should not throw an exception because of an unsupported flag. + */ + readonly attribute unsigned long flags; + + /** + * The server-supplied realm of the authentication as defined in RFC 2617. + * Can be the empty string if the protocol does not support realms. + * Otherwise, this is a human-readable string like "Secret files". + */ + readonly attribute AString realm; + + /** + * The authentication scheme used for this request, if applicable. If the + * protocol for this authentication does not support schemes, this will be + * the empty string. Otherwise, this will be a string such as "basic" or + * "digest". This string will always be in lowercase. + */ + readonly attribute AUTF8String authenticationScheme; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the username entered by + * the user. + * This field can only be changed if the #ONLY_PASSWORD flag is not set. + */ + attribute AString username; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * The password should not be shown in clear. + * On return, this parameter should contain the password entered by + * the user. + */ + attribute AString password; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the domain entered by + * the user. + * This attribute is only used if flags include #NEED_DOMAIN. + */ + attribute AString domain; +}; + diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl new file mode 100644 index 000000000..8a446cb21 --- /dev/null +++ b/netwerk/base/nsIAuthModule.idl @@ -0,0 +1,145 @@ +/* vim:set ts=4 sw=4 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)] +interface nsIAuthModule : nsISupports +{ + /** + * Default behavior. + */ + const unsigned long REQ_DEFAULT = 0; + + /** + * Client and server will be authenticated. + */ + const unsigned long REQ_MUTUAL_AUTH = (1 << 0); + + /** + * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH + * flag may also need to be specified in order for this flag to take + * effect. + */ + const unsigned long REQ_DELEGATE = (1 << 1); + + /** + * The authentication is required for a proxy connection. + */ + const unsigned long REQ_PROXY_AUTH = (1 << 2); + + /** + * Flags used for telemetry. + */ + const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0; + const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1; + const unsigned long NTLM_MODULE_WIN_API_PROXY = 2; + const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3; + const unsigned long NTLM_MODULE_GENERIC_PROXY = 4; + const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5; + const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6; + const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7; + + /** Other flags may be defined in the future */ + + /** + * Called to initialize an auth module. The other methods cannot be called + * unless this method succeeds. + * + * @param aServiceName + * the service name, which may be null if not applicable (e.g., for + * NTLM, this parameter should be null). + * @param aServiceFlags + * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT + * for default behavior). + * @param aDomain + * the authentication domain, which may be null if not applicable. + * @param aUsername + * the user's login name + * @param aPassword + * the user's password + */ + void init(in string aServiceName, + in unsigned long aServiceFlags, + in wstring aDomain, + in wstring aUsername, + in wstring aPassword); + + /** + * Called to get the next token in a sequence of authentication steps. + * + * @param aInToken + * A buffer containing the input token (e.g., a challenge from a + * server). This may be null. + * @param aInTokenLength + * The length of the input token. + * @param aOutToken + * If getNextToken succeeds, then aOutToken will point to a buffer + * to be sent in response to the server challenge. The length of + * this buffer is given by aOutTokenLength. The buffer at aOutToken + * must be recycled with a call to free. + * @param aOutTokenLength + * If getNextToken succeeds, then aOutTokenLength contains the + * length of the buffer (number of bytes) pointed to by aOutToken. + */ + void getNextToken([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + /** + * Once a security context has been established through calls to GetNextToken() + * it may be used to protect data exchanged between client and server. Calls + * to Wrap() are used to protect items of data to be sent to the server. + * + * @param aInToken + * A buffer containing the data to be sent to the server + * @param aInTokenLength + * The length of the input token + * @param confidential + * If set to true, Wrap() will encrypt the data, otherwise data will + * just be integrity protected (checksummed) + * @param aOutToken + * A buffer containing the resulting data to be sent to the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication + * mechanism does not support security layers. + */ + void wrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + in boolean confidential, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + + /** + * Unwrap() is used to unpack, decrypt, and verify the checksums on data + * returned by a server when security layers are in use. + * + * @param aInToken + * A buffer containing the data received from the server + * @param aInTokenLength + * The length of the input token + * @param aOutToken + * A buffer containing the plaintext data from the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying + * authentication mechanism does not support security layers. + */ + void unwrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); +}; + +%{C++ +/** + * nsIAuthModule implementations are registered under the following contract + * ID prefix: + */ +#define NS_AUTH_MODULE_CONTRACTID_PREFIX \ + "@mozilla.org/network/auth-module;1?name=" +%} diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl new file mode 100644 index 000000000..bb9f88f60 --- /dev/null +++ b/netwerk/base/nsIAuthPrompt.idl @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrompt; + +[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)] +interface nsIAuthPrompt : nsISupports +{ + const uint32_t SAVE_PASSWORD_NEVER = 0; + const uint32_t SAVE_PASSWORD_FOR_SESSION = 1; + const uint32_t SAVE_PASSWORD_PERMANENTLY = 2; + + /** + * Puts up a text input dialog with OK and Cancel buttons. + * Note: prompt uses separate args for the "in" and "out" values of the + * input field, whereas the other functions use a single inout arg. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param defaultText The default text to display in the text input box. + * @param result The value entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean prompt(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + in wstring defaultText, + out wstring result); + + /** + * Puts up a username/password dialog with OK and Cancel buttons. + * Puts up a password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param user The username entered in the dialog. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring user, + inout wstring pwd); + + /** + * Puts up a password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test. If a username is + * specified (http://user@site.com) it will be used + * when matching existing logins or saving new ones. + * If no username is specified, only password-only + * logins will be matched or saved. + * Note: if a username is specified, the username + * should be escaped. + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring pwd); +}; diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl new file mode 100644 index 000000000..23c27c3d1 --- /dev/null +++ b/netwerk/base/nsIAuthPrompt2.idl @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPromptCallback; +interface nsIChannel; +interface nsICancelable; +interface nsIAuthInformation; + +/** + * An interface allowing to prompt for a username and password. This interface + * is usually acquired using getInterface on notification callbacks or similar. + * It can be used to prompt users for authentication information, either + * synchronously or asynchronously. + */ +[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)] +interface nsIAuthPrompt2 : nsISupports +{ + /** @name Security Levels */ + /* @{ */ + /** + * The password will be sent unencrypted. No security provided. + */ + const uint32_t LEVEL_NONE = 0; + /** + * Password will be sent encrypted, but the connection is otherwise + * insecure. + */ + const uint32_t LEVEL_PW_ENCRYPTED = 1; + /** + * The connection, both for password and data, is secure. + */ + const uint32_t LEVEL_SECURE = 2; + /* @} */ + + /** + * Requests a username and a password. Implementations will commonly show a + * dialog with a username and password field, depending on flags also a + * domain field. + * + * @param aChannel + * The channel that requires authentication. + * @param level + * One of the level constants from above. See there for descriptions + * of the levels. + * @param authInfo + * Authentication information object. The implementation should fill in + * this object with the information entered by the user before + * returning. + * + * @retval true + * Authentication can proceed using the values in the authInfo + * object. + * @retval false + * Authentication should be cancelled, usually because the user did + * not provide username/password. + * + * @note Exceptions thrown from this function will be treated like a + * return value of false. + */ + boolean promptAuth(in nsIChannel aChannel, + in uint32_t level, + in nsIAuthInformation authInfo); + + /** + * Asynchronously prompt the user for a username and password. + * This has largely the same semantics as promptUsernameAndPassword(), + * but must return immediately after calling and return the entered + * data in a callback. + * + * If the user closes the dialog using a cancel button or similar, + * the callback's nsIAuthPromptCallback::onAuthCancelled method must be + * called. + * Calling nsICancelable::cancel on the returned object SHOULD close the + * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided + * callback. + * + * This implementation may: + * + * 1) Coalesce identical prompts. This means prompts that are guaranteed to + * want the same auth information from the user. A single prompt will be + * shown; then the callbacks for all the coalesced prompts will be notified + * with the resulting auth information. + * 2) Serialize prompts that are all in the same "context" (this might mean + * application-wide, for a given window, or something else depending on + * the user interface) so that the user is not deluged with prompts. + * + * @throw + * This method may throw any exception when the prompt fails to queue e.g + * because of out-of-memory error. It must not throw when the prompt + * could already be potentially shown to the user. In that case information + * about the failure has to come through the callback. This way we + * prevent multiple dialogs shown to the user because consumer may fall + * back to synchronous prompt on synchronous failure of this method. + */ + nsICancelable asyncPromptAuth(in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo); +}; diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl new file mode 100644 index 000000000..e763d4714 --- /dev/null +++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPrompt; +interface nsIAuthPrompt2; + +/** + * An interface for wrapping nsIAuthPrompt interfaces to make + * them usable via an nsIAuthPrompt2 interface. + */ +[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)] +interface nsIAuthPromptAdapterFactory : nsISupports +{ + /** + * Wrap an object implementing nsIAuthPrompt so that it's usable via + * nsIAuthPrompt2. + */ + nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt); +}; diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl new file mode 100644 index 000000000..bf9f2f2ac --- /dev/null +++ b/netwerk/base/nsIAuthPromptCallback.idl @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthInformation; + +/** + * Interface for callback methods for the asynchronous nsIAuthPrompt2 method. + * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync + * returns successfully. They MUST NOT call any method on this interface before + * promptPasswordAsync returns. + */ +[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)] +interface nsIAuthPromptCallback : nsISupports +{ + /** + * Authentication information is available. + * + * @param aContext + * The context as passed to promptPasswordAsync + * @param aAuthInfo + * Authentication information. Must be the same object that was passed + * to promptPasswordAsync. + * + * @note Any exceptions thrown from this method should be ignored. + */ + void onAuthAvailable(in nsISupports aContext, + in nsIAuthInformation aAuthInfo); + + /** + * Notification that the prompt was cancelled. + * + * @param aContext + * The context that was passed to promptPasswordAsync. + * @param userCancel + * If false, this prompt was cancelled by calling the + * the cancel method on the nsICancelable; otherwise, + * it was cancelled by the user. + */ + void onAuthCancelled(in nsISupports aContext, in boolean userCancel); +}; + diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl new file mode 100644 index 000000000..e8ff122ec --- /dev/null +++ b/netwerk/base/nsIAuthPromptProvider.idl @@ -0,0 +1,34 @@ +/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)] +interface nsIAuthPromptProvider : nsISupports +{ + /** + * Normal (non-proxy) prompt request. + */ + const uint32_t PROMPT_NORMAL = 0; + + /** + * Proxy auth request. + */ + const uint32_t PROMPT_PROXY = 1; + + /** + * Request a prompt interface for the given prompt reason; + * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or + * available for the given reason. + * + * @param aPromptReason The reason for the auth prompt; + * one of #PROMPT_NORMAL or #PROMPT_PROXY + * @param iid The desired interface, e.g. + * NS_GET_IID(nsIAuthPrompt2). + * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE + */ + void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); +}; diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl new file mode 100644 index 000000000..df560d02f --- /dev/null +++ b/netwerk/base/nsIBackgroundFileSaver.idl @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIArray; +interface nsIBackgroundFileSaverObserver; +interface nsIFile; + +/** + * Allows saving data to a file, while handling all the input/output on a + * background thread, including the initial file name assignment and any + * subsequent renaming of the target file. + * + * This interface is designed for file downloads. Generally, they start in the + * temporary directory, while the user is selecting the final name. Then, they + * are moved to the chosen target directory with a ".part" extension appended to + * the file name. Finally, they are renamed when the download is completed. + * + * Components implementing both nsIBackgroundFileSaver and nsIStreamListener + * allow data to be fed using an implementation of OnDataAvailable that never + * blocks the calling thread. They suspend the request that drives the stream + * listener in case too much data is being fed, and resume it when the data has + * been written. Calling OnStopRequest does not necessarily close the target + * file, and the Finish method must be called to complete the operation. + * + * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream + * allow data to be fed directly to the non-blocking output stream, that however + * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed. + * Closing the output stream does not necessarily close the target file, and the + * Finish method must be called to complete the operation. + * + * @remarks Implementations may require the consumer to always call Finish. If + * the object reference is released without calling Finish, a memory + * leak may occur, and the target file might be kept locked. All + * public methods of the interface may only be called from the main + * thread. + */ +[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)] +interface nsIBackgroundFileSaver : nsISupports +{ + /** + * This observer receives notifications when the target file name changes and + * when the operation completes, successfully or not. + * + * @remarks A strong reference to the observer is held. Notification events + * are dispatched to the thread that created the object that + * implements nsIBackgroundFileSaver. + */ + attribute nsIBackgroundFileSaverObserver observer; + + /** + * An nsIArray of nsIX509CertList, representing a chain of X.509 signatures on + * the downloaded file. Each list may belong to a different signer and contain + * certificates all the way up to the root. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case this is called before the onSaveComplete method has been + * called to notify success, or enableSignatureInfo has not been + * called. + */ + readonly attribute nsIArray signatureInfo; + + /** + * The SHA-256 hash, in raw bytes, associated with the data that was saved. + * + * In case the enableAppend method has been called, the hash computation + * includes the contents of the existing file, if any. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case the enableSha256 method has not been called, or before the + * onSaveComplete method has been called to notify success. + */ + readonly attribute ACString sha256Hash; + + /** + * Instructs the component to compute the signatureInfo of the target file, + * and make it available in the signatureInfo property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSignatureInfo(); + + /** + * Instructs the component to compute the SHA-256 hash of the target file, and + * make it available in the sha256Hash property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSha256(); + + /** + * Instructs the component to append data to the initial target file, that + * will be specified by the first call to the setTarget method, instead of + * overwriting the file. + * + * If the initial target file does not exist, this method has no effect. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableAppend(); + + /** + * Sets the name of the output file to be written. The target can be changed + * after data has already been fed, in which case the existing file will be + * moved to the new destination. + * + * In case the specified file already exists, and this method is called for + * the first time, the file may be either overwritten or appended to, based on + * whether the enableAppend method was called. Subsequent calls always + * overwrite the specified target file with the previously saved data. + * + * No file will be written until this function is called at least once. It's + * recommended not to feed any data until the output file is set. + * + * If an input/output error occurs with the specified file, the save operation + * fails. Failure is notified asynchronously through the observer. + * + * @param aTarget + * New output file to be written. + * @param aKeepPartial + * Indicates whether aFile should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. This is + * generally set for downloads that use temporary ".part" files. + */ + void setTarget(in nsIFile aTarget, in bool aKeepPartial); + + /** + * Terminates access to the output file, then notifies the observer with the + * specified status code. A failure code will force the operation to be + * canceled, in which case the output file will be deleted if requested. + * + * This forces the involved streams to be closed, thus no more data should be + * fed to the component after this method has been called. + * + * This is the last method that should be called on this object, and the + * target file name cannot be changed anymore after this method has been + * called. Conversely, before calling this method, the file can still be + * renamed even if all the data has been fed. + * + * @param aStatus + * Result code that determines whether the operation should succeed or + * be canceled, and is notified to the observer. If the operation + * fails meanwhile for other reasons, or the observer has been already + * notified of completion, this status code is ignored. + */ + void finish(in nsresult aStatus); +}; + +[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)] +interface nsIBackgroundFileSaverObserver : nsISupports +{ + /** + * Called when the name of the output file has been determined. This function + * may be called more than once if the target file is renamed while saving. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aTarget + * Name of the file that is being written. + */ + void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget); + + /** + * Called when the operation completed, and the target file has been closed. + * If the operation succeeded, the target file is ready to be used, otherwise + * it might have been already deleted. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aStatus + * Result code that determines whether the operation succeeded or + * failed, as well as the failure reason. + */ + void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus); +}; diff --git a/netwerk/base/nsIBrowserSearchService.idl b/netwerk/base/nsIBrowserSearchService.idl new file mode 100644 index 000000000..045973e0c --- /dev/null +++ b/netwerk/base/nsIBrowserSearchService.idl @@ -0,0 +1,530 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIInputStream; + +[scriptable, uuid(5799251f-5b55-4df7-a9e7-0c27812c469a)] +interface nsISearchSubmission : nsISupports +{ + /** + * The POST data associated with a search submission, wrapped in a MIME + * input stream. May be null. + */ + readonly attribute nsIInputStream postData; + + /** + * The URI to submit a search to. + */ + readonly attribute nsIURI uri; +}; + +[scriptable, uuid(620bd920-0491-48c8-99a8-d6047e64802d)] +interface nsISearchEngine : nsISupports +{ + /** + * Gets a nsISearchSubmission object that contains information about what to + * send to the search engine, including the URI and postData, if applicable. + * + * @param data + * Data to add to the submission object. + * i.e. the search terms. + * + * @param responseType [optional] + * The MIME type that we'd like to receive in response + * to this submission. If null, will default to "text/html". + * + * @param purpose [optional] + * A string meant to indicate the context of the search request. This + * allows the search service to provide a different nsISearchSubmission + * depending on e.g. where the search is triggered in the UI. + * + * @returns A nsISearchSubmission object that contains information about what + * to send to the search engine. If no submission can be + * obtained for the given responseType, returns null. + */ + nsISearchSubmission getSubmission(in AString data, + [optional] in AString responseType, + [optional] in AString purpose); + + /** + * Adds a parameter to the search engine's submission data. This should only + * be called on engines created via addEngineWithDetails. + * + * @param name + * The parameter's name. Must not be null. + * + * @param value + * The value to pass. If value is "{searchTerms}", it will be + * substituted with the user-entered data when retrieving the + * submission. Must not be null. + * + * @param responseType + * Since an engine can have several different request URLs, + * differentiated by response types, this parameter selects + * a request to add parameters to. If null, will default + * to "text/html" + * + * @throws NS_ERROR_FAILURE if the search engine is read-only. + * @throws NS_ERROR_INVALID_ARG if name or value are null. + */ + void addParam(in AString name, in AString value, in AString responseType); + + /** + * Determines whether the engine can return responses in the given + * MIME type. Returns true if the engine spec has a URL with the + * given responseType, false otherwise. + * + * @param responseType + * The MIME type to check for + */ + boolean supportsResponseType(in AString responseType); + + /** + * Returns a string with the URL to an engine's icon matching both width and + * height. Returns null if icon with specified dimensions is not found. + * + * @param width + * Width of the requested icon. + * @param height + * Height of the requested icon. + */ + AString getIconURLBySize(in long width, in long height); + + /** + * Gets an array of all available icons. Each entry is an object with + * width, height and url properties. width and height are numeric and + * represent the icon's dimensions. url is a string with the URL for + * the icon. + */ + jsval getIcons(); + + /** + * Opens a speculative connection to the engine's search URI + * (and suggest URI, if different) to reduce request latency + * + * @param options + * An object that must contain the following fields: + * {window} the content window for the window performing the search + * + * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required + * elemeents + */ + void speculativeConnect(in jsval options); + + /** + * An optional shortcut alias for the engine. + * When non-null, this is a unique identifier. + */ + attribute AString alias; + + /** + * A text description describing the engine. + */ + readonly attribute AString description; + + /** + * Whether the engine should be hidden from the user. + */ + attribute boolean hidden; + + /** + * A nsIURI corresponding to the engine's icon, stored locally. May be null. + */ + readonly attribute nsIURI iconURI; + + /** + * The display name of the search engine. This is a unique identifier. + */ + readonly attribute AString name; + + /** + * A URL string pointing to the engine's search form. + */ + readonly attribute AString searchForm; + + /** + * An optional unique identifier for this search engine within the context of + * the distribution, as provided by the distributing entity. + */ + readonly attribute AString identifier; + + /** + * Gets a string representing the hostname from which search results for a + * given responseType are returned, minus the leading "www." (if present). + * This can be specified as an url attribute in the engine description file, + * but will default to host from the 's template otherwise. + * + * @param responseType [optional] + * The MIME type to get resultDomain for. Defaults to "text/html". + * + * @return the resultDomain for the given responseType. + */ + AString getResultDomain([optional] in AString responseType); +}; + +[scriptable, uuid(0dc93e51-a7bf-4a16-862d-4b3469ff6206)] +interface nsISearchParseSubmissionResult : nsISupports +{ + /** + * The search engine associated with the URL passed in to + * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent + * a search submission. + */ + readonly attribute nsISearchEngine engine; + + /** + * String containing the sought terms. This can be an empty string in case no + * terms were specified or the URL does not represent a search submission. + */ + readonly attribute AString terms; + + /** + * The offset of the string |terms| in the URL passed in to + * nsISearchEngine::parseSubmissionURL, or -1 if the URL does not represent + * a search submission. + */ + readonly attribute long termsOffset; + + /** + * The length of the |terms| in the original encoding of the URL passed in to + * nsISearchEngine::parseSubmissionURL. If the search term in the original + * URL is encoded then this will be bigger than |terms.length|. + */ + readonly attribute long termsLength; +}; + +[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)] +interface nsISearchInstallCallback : nsISupports +{ + const unsigned long ERROR_UNKNOWN_FAILURE = 0x1; + const unsigned long ERROR_DUPLICATE_ENGINE = 0x2; + + /** + * Called to indicate that the engine addition process succeeded. + * + * @param engine + * The nsISearchEngine object that was added (will not be null). + */ + void onSuccess(in nsISearchEngine engine); + + /** + * Called to indicate that the engine addition process failed. + * + * @param errorCode + * One of the ERROR_* values described above indicating the cause of + * the failure. + */ + void onError(in unsigned long errorCode); +}; + +/** + * Callback for asynchronous initialization of nsIBrowserSearchService + */ +[scriptable, function, uuid(02256156-16e4-47f1-9979-76ff98ceb590)] +interface nsIBrowserSearchInitObserver : nsISupports +{ + /** + * Called once initialization of the browser search service is complete. + * + * @param aStatus The status of that service. + */ + void onInitComplete(in nsresult aStatus); +}; + +[scriptable, uuid(150ef720-bbe2-4169-b9f3-ef7ec0654ced)] +interface nsIBrowserSearchService : nsISupports +{ + /** + * Start asynchronous initialization. + * + * The callback is triggered once initialization is complete, which may be + * immediately, if initialization has already been completed by some previous + * call to this method. The callback is always invoked asynchronously. + * + * @param aObserver An optional object observing the end of initialization. + */ + void init([optional] in nsIBrowserSearchInitObserver aObserver); + + /** + * Determine whether initialization has been completed. + * + * Clients of the service can use this attribute to quickly determine whether + * initialization is complete, and decide to trigger some immediate treatment, + * to launch asynchronous initialization or to bailout. + * + * Note that this attribute does not indicate that initialization has succeeded. + * + * @return |true| if the search service is now initialized, |false| if + * initialization has not been triggered yet. + */ + readonly attribute bool isInitialized; + + /** + * Resets the default engine to its original value. + */ + void resetToOriginalDefaultEngine(); + + /** + * Checks if an EngineURL of type URLTYPE_SEARCH_HTML exists for + * any engine, with a matching method, template URL, and query params. + * + * @param method + * The HTTP request method used when submitting a search query. + * Must be a case insensitive value of either "get" or "post". + * + * @param url + * The URL to which search queries should be sent. + * Must not be null. + * + * @param formData + * The un-sorted form data used as query params. + */ + boolean hasEngineWithURL(in AString method, in AString url, in jsval formData); + + /** + * Adds a new search engine from the file at the supplied URI, optionally + * asking the user for confirmation first. If a confirmation dialog is + * shown, it will offer the option to begin using the newly added engine + * right away. + * + * @param engineURL + * The URL to the search engine's description file. + * + * @param dataType + * Obsolete, the value is ignored. + * + * @param iconURL + * A URL string to an icon file to be used as the search engine's + * icon. This value may be overridden by an icon specified in the + * engine description file. + * + * @param confirm + * A boolean value indicating whether the user should be asked for + * confirmation before this engine is added to the list. If this + * value is false, the engine will be added to the list upon successful + * load, but it will not be selected as the current engine. + * + * @param callback + * A nsISearchInstallCallback that will be notified when the + * addition is complete, or if the addition fails. It will not be + * called if addEngine throws an exception. + * + * @throws NS_ERROR_FAILURE if the description file cannot be successfully + * loaded. + */ + void addEngine(in AString engineURL, in long dataType, in AString iconURL, + in boolean confirm, [optional] in nsISearchInstallCallback callback); + + /** + * Adds a new search engine, without asking the user for confirmation and + * without starting to use it right away. + * + * @param name + * The search engine's name. Must be unique. Must not be null. + * + * @param iconURL + * Optional: A URL string pointing to the icon to be used to represent + * the engine. + * + * @param alias + * Optional: A unique shortcut that can be used to retrieve the + * search engine. + * + * @param description + * Optional: a description of the search engine. + * + * @param method + * The HTTP request method used when submitting a search query. + * Must be a case insensitive value of either "get" or "post". + * + * @param url + * The URL to which search queries should be sent. + * Must not be null. + * + * @param extensionID [optional] + * Optional: The correct extensionID if called by an add-on. + */ + void addEngineWithDetails(in AString name, + in AString iconURL, + in AString alias, + in AString description, + in AString method, + in AString url, + [optional] in AString extensionID); + + /** + * Un-hides all engines installed in the directory corresponding to + * the directory service's NS_APP_SEARCH_DIR key. (i.e. the set of + * engines returned by getDefaultEngines) + */ + void restoreDefaultEngines(); + + /** + * Returns an engine with the specified alias. + * + * @param alias + * The search engine's alias. + * @returns The corresponding nsISearchEngine object, or null if it doesn't + * exist. + */ + nsISearchEngine getEngineByAlias(in AString alias); + + /** + * Returns an engine with the specified name. + * + * @param aEngineName + * The name of the engine. + * @returns The corresponding nsISearchEngine object, or null if it doesn't + * exist. + */ + nsISearchEngine getEngineByName(in AString aEngineName); + + /** + * Returns an array of all installed search engines. + * + * @returns an array of nsISearchEngine objects. + */ + void getEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Returns an array of all installed search engines whose hidden attribute is + * false. + * + * @returns an array of nsISearchEngine objects. + */ + void getVisibleEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Returns an array of all default search engines. This includes all loaded + * engines that aren't in the user's profile directory + * (NS_APP_USER_SEARCH_DIR). + * + * @returns an array of nsISearchEngine objects. + */ + void getDefaultEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Moves a visible search engine. + * + * @param engine + * The engine to move. + * @param newIndex + * The engine's new index in the set of visible engines. + * + * @throws NS_ERROR_FAILURE if newIndex is out of bounds, or if engine is + * hidden. + */ + void moveEngine(in nsISearchEngine engine, in long newIndex); + + /** + * Removes the search engine. If the search engine is installed in a global + * location, this will just hide the engine. If the engine is in the user's + * profile directory, it will be removed from disk. + * + * @param engine + * The engine to remove. + */ + void removeEngine(in nsISearchEngine engine); + + /** + * The original Engine object that is the default for this region, + * ignoring changes the user may have subsequently made. + */ + readonly attribute nsISearchEngine originalDefaultEngine; + + /** + * Alias for the currentEngine attribute, kept for add-on compatibility. + */ + attribute nsISearchEngine defaultEngine; + + /** + * The currently active search engine. + * Unless the application doesn't ship any search plugin, this should never + * be null. If the currently active engine is removed, this attribute will + * fallback first to the original default engine if it's not hidden, then to + * the first visible engine, and as a last resort it will unhide the original + * default engine. + */ + attribute nsISearchEngine currentEngine; + + /** + * Gets a representation of the default engine in an anonymized JSON + * string suitable for recording in the Telemetry environment. + * + * @return an object containing anonymized info about the default engine: + * name, loadPath, submissionURL (for default engines). + */ + jsval getDefaultEngineInfo(); + + /** + * Determines if the provided URL represents results from a search engine, and + * provides details about the match. + * + * The lookup mechanism checks whether the domain name and path of the + * provided HTTP or HTTPS URL matches one of the known values for the visible + * search engines. The match does not depend on which of the schemes is used. + * The expected URI parameter for the search terms must exist in the query + * string, but other parameters are ignored. + * + * @param url + * String containing the URL to parse, for example + * "https://www.google.com/search?q=terms". + */ + nsISearchParseSubmissionResult parseSubmissionURL(in AString url); +}; + +%{ C++ +/** + * The observer topic to listen to for actions performed on installed + * search engines. + */ +#define SEARCH_ENGINE_TOPIC "browser-search-engine-modified" + +/** + * Sent when an engine is removed from the data store. + */ +#define SEARCH_ENGINE_REMOVED "engine-removed" + +/** + * Sent when an engine is changed. This includes when the engine's "hidden" + * property is changed. + */ +#define SEARCH_ENGINE_CHANGED "engine-changed" + +/** + * Sent when an engine is added to the list of available engines. + */ +#define SEARCH_ENGINE_ADDED "engine-added" + +/** + * Sent when a search engine being installed from a remote plugin description + * file is completely loaded. This is used internally by the search service as + * an indication of when the engine can be added to the internal store, and + * therefore should not be used to detect engine availability. It is always + * followed by an "added" notification. + */ +#define SEARCH_ENGINE_LOADED "engine-loaded" + +/** + * Sent when the "current" engine is changed. + */ +#define SEARCH_ENGINE_CURRENT "engine-current"; + +/** + * Sent when the "default" engine is changed. + */ +#define SEARCH_ENGINE_DEFAULT "engine-default"; + + + +%} diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl new file mode 100644 index 000000000..60b9768b9 --- /dev/null +++ b/netwerk/base/nsIBufferedStreams.idl @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" + +/** + * An input stream that reads ahead and keeps a buffer coming from another input + * stream so that fewer accesses to the underlying stream are necessary. + */ +[scriptable, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedInputStream : nsIInputStream +{ + /** + * @param fillFromStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIInputStream fillFromStream, + in unsigned long bufferSize); +}; + +/** + * An output stream that stores up data to write out to another output stream + * and does the entire write only when the buffer is full, so that fewer writes + * to the underlying output stream are necessary. + */ +[scriptable, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedOutputStream : nsIOutputStream +{ + /** + * @param sinkToStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIOutputStream sinkToStream, + in unsigned long bufferSize); +}; diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl new file mode 100644 index 000000000..b558016a5 --- /dev/null +++ b/netwerk/base/nsIByteRangeRequest.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)] +interface nsIByteRangeRequest : nsISupports +{ + /** + * Returns true IFF this request is a byte range request, otherwise it + * returns false (This is effectively the same as checking to see if + * |startRequest| is zero and |endRange| is the content length.) + */ + readonly attribute boolean isByteRangeRequest; + + /** + * Absolute start position in remote file for this request. + */ + readonly attribute long long startRange; + + /** + * Absolute end postion in remote file for this request + */ + readonly attribute long long endRange; +}; diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl new file mode 100644 index 000000000..f6d3c7b73 --- /dev/null +++ b/netwerk/base/nsICacheInfoChannel.idl @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIOutputStream; + +[scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)] +interface nsICacheInfoChannel : nsISupports +{ + /** + * Get expiration time from cache token. This attribute is equivalent to + * nsICachingChannel.cacheToken.expirationTime. + */ + readonly attribute uint32_t cacheTokenExpirationTime; + + /** + * Set/get charset of cache entry. Accessing this attribute is equivalent to + * calling nsICachingChannel.cacheToken.getMetaDataElement("charset") and + * nsICachingChannel.cacheToken.setMetaDataElement("charset"). + */ + attribute ACString cacheTokenCachedCharset; + + /** + * TRUE if this channel's data is being loaded from the cache. This value + * is undefined before the channel fires its OnStartRequest notification + * and after the channel fires its OnStopRequest notification. + */ + boolean isFromCache(); + + /** + * Set/get the cache key... uniquely identifies the data in the cache + * for this channel. Holding a reference to this key does NOT prevent + * the cached data from being removed. + * + * A cache key retrieved from a particular instance of nsICacheInfoChannel + * could be set on another instance of nsICacheInfoChannel provided the + * underlying implementations are compatible and provided the new + * channel instance was created with the same URI. The implementation of + * nsICacheInfoChannel would be expected to use the cache entry identified + * by the cache token. Depending on the value of nsIRequest::loadFlags, + * the cache entry may be validated, overwritten, or simply read. + * + * The cache key may be NULL indicating that the URI of the channel is + * sufficient to locate the same cache entry. Setting a NULL cache key + * is likewise valid. + */ + attribute nsISupports cacheKey; + + /** + * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set, + * but without affecting the loads for the entire loadGroup in case of this + * channel being the default load group's channel. + */ + attribute boolean allowStaleCacheContent; + + /** + * Calling this method instructs the channel to serve the alternative data + * if that was previously saved in the cache, otherwise it will serve the + * real data. + * Must be called before AsyncOpen. + */ + void preferAlternativeDataType(in ACString type); + + /** + * Holds the type of the alternative data representation that the channel + * is returning. + * Is empty string if no alternative data representation was requested, or + * if the requested representation wasn't found in the cache. + * Can only be called during or after OnStartRequest. + */ + readonly attribute ACString alternativeDataType; + + /** + * Opens and returns an output stream that a consumer may use to save an + * alternate representation of the data. + * Must be called after the OnStopRequest that delivered the real data. + * The consumer may choose to replace the saved alt representation. + * Opening the output stream will fail if there are any open input streams + * reading the already saved alt representation. + */ + nsIOutputStream openAlternativeOutputStream(in ACString type); +}; diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl new file mode 100644 index 000000000..63f65b1a4 --- /dev/null +++ b/netwerk/base/nsICachingChannel.idl @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICacheInfoChannel.idl" + +interface nsIFile; + +/** + * A channel may optionally implement this interface to allow clients + * to affect its behavior with respect to how it uses the cache service. + * + * This interface provides: + * 1) Support for "stream as file" semantics (for JAR and plugins). + * 2) Support for "pinning" cached data in the cache (for printing and save-as). + * 3) Support for uniquely identifying cached data in cases when the URL + * is insufficient (e.g., HTTP form submission). + */ +[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)] +interface nsICachingChannel : nsICacheInfoChannel +{ + /** + * Set/get the cache token... uniquely identifies the data in the cache. + * Holding a reference to this token prevents the cached data from being + * removed. + * + * A cache token retrieved from a particular instance of nsICachingChannel + * could be set on another instance of nsICachingChannel provided the + * underlying implementations are compatible. The implementation of + * nsICachingChannel would be expected to only read from the cache entry + * identified by the cache token and not try to validate it. + * + * The cache token can be QI'd to a nsICacheEntryInfo if more detail + * about the cache entry is needed (e.g., expiration time). + */ + attribute nsISupports cacheToken; + + /** + * The same as above but accessing the offline app cache token if there + * is any. + * + * @throws + * NS_ERROR_NOT_AVAILABLE when there is not offline cache token + */ + attribute nsISupports offlineCacheToken; + + /** + * Instructs the channel to only store the metadata of the entry, and not + * the content. When reading an existing entry, this automatically sets + * LOAD_ONLY_IF_MODIFIED flag. + * Must be called before asyncOpen(). + */ + attribute boolean cacheOnlyMetadata; + + /** + * Tells the channel to use the pinning storage. + */ + attribute boolean pin; + + /** + * Overrides cache validation for a time specified in seconds. + * + * @param aSecondsToTheFuture + * + */ + void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture); + + /************************************************************************** + * Caching channel specific load flags: + */ + + /** + * This load flag inhibits fetching from the net. An error of + * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's + * onStopRequest if network IO is necessary to complete the request. + * + * This flag can be used to find out whether fetching this URL would + * cause validation of the cache entry via the network. + * + * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all + * loads to fail. This flag differs from LOAD_ONLY_FROM_CACHE in that + * this flag fails the load if validation is required while + * LOAD_ONLY_FROM_CACHE skips validation where possible. + */ + const unsigned long LOAD_NO_NETWORK_IO = 1 << 26; + + /** + * This load flag causes the offline cache to be checked when fetching + * a request. It will be set automatically if the browser is offline. + * + * This flag will not be transferred through a redirect. + */ + const unsigned long LOAD_CHECK_OFFLINE_CACHE = 1 << 27; + + /** + * This load flag causes the local cache to be skipped when fetching a + * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load + * (i.e., it does not affect proxy caches). + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28; + + /** + * This load flag causes the local cache to be skipped if the request + * would otherwise block waiting to access the cache. + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29; + + /** + * This load flag inhibits fetching from the net if the data in the cache + * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent + * to the listener's onStopRequest in this case. This flag is set + * automatically when the application is offline. + */ + const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30; + + /** + * This load flag controls what happens when a document would be loaded + * from the cache to satisfy a call to AsyncOpen. If this attribute is + * set to TRUE, then the document will not be loaded from the cache. A + * stream listener can check nsICachingChannel::isFromCache to determine + * if the AsyncOpen will actually result in data being streamed. + * + * If this flag has been set, and the request can be satisfied via the + * cache, then the OnDataAvailable events will be skipped. The listener + * will only see OnStartRequest followed by OnStopRequest. + */ + const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31; +}; diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl new file mode 100644 index 000000000..c558dc6e2 --- /dev/null +++ b/netwerk/base/nsICancelable.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface provides a means to cancel an operation that is in progress. + */ +[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)] +interface nsICancelable : nsISupports +{ + /** + * Call this method to request that this object abort whatever operation it + * may be performing. + * + * @param aReason + * Pass a failure code to indicate the reason why this operation is + * being canceled. It is an error to pass a success code. + */ + void cancel(in nsresult aReason); +}; diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl new file mode 100644 index 000000000..94d9d6e9a --- /dev/null +++ b/netwerk/base/nsICaptivePortalService.idl @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)] +interface nsICaptivePortalServiceCallback : nsISupports +{ + /** + * Invoke callbacks after captive portal detection finished. + */ + void complete(in bool success, in nsresult error); +}; + +/** + * Service used for captive portal detection. + * The service is only active in the main process. It is also available in the + * content process, but only to mirror the captive portal state from the main + * process. + */ +[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)] +interface nsICaptivePortalService : nsISupports +{ + const long UNKNOWN = 0; + const long NOT_CAPTIVE = 1; + const long UNLOCKED_PORTAL = 2; + const long LOCKED_PORTAL = 3; + + /** + * Called from XPCOM to trigger a captive portal recheck. + * A network request will only be performed if no other checks are currently + * ongoing. + * Will not do anything if called in the content process. + */ + void recheckCaptivePortal(); + + /** + * Returns the state of the captive portal. + */ + readonly attribute long state; + + /** + * Returns the time difference between NOW and the last time a request was + * completed in milliseconds. + */ + readonly attribute unsigned long long lastChecked; +}; + +%{C++ +/** + * This observer notification will be emitted when the captive portal state + * changes. After receiving it, the ContentParent will send an IPC message + * to the ContentChild, which will set the state in the captive portal service + * in the child. + */ +#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state" + +%} diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl new file mode 100644 index 000000000..743e94292 --- /dev/null +++ b/netwerk/base/nsIChannel.idl @@ -0,0 +1,357 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" +#include "nsILoadInfo.idl" + +interface nsIURI; +interface nsIInterfaceRequestor; +interface nsIInputStream; +interface nsIStreamListener; + +%{C++ +#include "nsCOMPtr.h" +%} + +/** + * The nsIChannel interface allows clients to construct "GET" requests for + * specific protocols, and manage them in a uniform way. Once a channel is + * created (via nsIIOService::newChannel), parameters for that request may + * be set by using the channel attributes, or by QI'ing to a subclass of + * nsIChannel for protocol-specific parameters. Then, the URI can be fetched + * by calling nsIChannel::open or nsIChannel::asyncOpen. + * + * After a request has been completed, the channel is still valid for accessing + * protocol-specific results. For example, QI'ing to nsIHttpChannel allows + * response headers to be retrieved for the corresponding http transaction. + * + * This interface must be used only from the XPCOM main thread. + */ +[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)] +interface nsIChannel : nsIRequest +{ + /** + * The original URI used to construct the channel. This is used in + * the case of a redirect or URI "resolution" (e.g. resolving a + * resource: URI to a file: URI) so that the original pre-redirect + * URI can still be obtained. This is never null. Attempts to + * set it to null must throw. + * + * NOTE: this is distinctly different from the http Referer (referring URI), + * which is typically the page that contained the original URI (accessible + * from nsIHttpChannel). + */ + attribute nsIURI originalURI; + + /** + * The URI corresponding to the channel. Its value is immutable. + */ + readonly attribute nsIURI URI; + + /** + * The owner, corresponding to the entity that is responsible for this + * channel. Used by the security manager to grant or deny privileges to + * mobile code loaded from this channel. + * + * NOTE: this is a strong reference to the owner, so if the owner is also + * holding a strong reference to the channel, care must be taken to + * explicitly drop its reference to the channel. + */ + attribute nsISupports owner; + + /** + * The notification callbacks for the channel. This is set by clients, who + * wish to provide a means to receive progress, status and protocol-specific + * notifications. If this value is NULL, the channel implementation may use + * the notification callbacks from its load group. The channel may also + * query the notification callbacks from its load group if its notification + * callbacks do not supply the requested interface. + * + * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt, + * and nsIAuthPrompt/nsIAuthPrompt2. + * + * When the channel is done, it must not continue holding references to + * this object. + * + * NOTE: A channel implementation should take care when "caching" an + * interface pointer queried from its notification callbacks. If the + * notification callbacks are changed, then a cached interface pointer may + * become invalid and may therefore need to be re-queried. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Transport-level security information (if any) corresponding to the + * channel. + * + * NOTE: In some circumstances TLS information is propagated onto + * non-nsIHttpChannel objects to indicate that their contents were likely + * delivered over TLS all the same. For example, document.open() may + * create an nsWyciwygChannel to store the data that will be written to the + * document. In that case, if the caller has TLS information, we propagate + * that info onto the nsWyciwygChannel given that it is likely that the + * caller will be writing data that was delivered over TLS to the document. + */ + readonly attribute nsISupports securityInfo; + + /** + * The MIME type of the channel's content if available. + * + * NOTE: the content type can often be wrongly specified (e.g., wrong file + * extension, wrong MIME type, wrong document type stored on a server, etc.), + * and the caller most likely wants to verify with the actual data. + * + * Setting contentType before the channel has been opened provides a hint + * to the channel as to what the MIME type is. The channel may ignore this + * hint in deciding on the actual MIME type that it will report. + * + * Setting contentType after onStartRequest has been fired or after open() + * is called will override the type determined by the channel. + * + * Setting contentType between the time that asyncOpen() is called and the + * time when onStartRequest is fired has undefined behavior at this time. + * + * The value of the contentType attribute is a lowercase string. A value + * assigned to this attribute will be parsed and normalized as follows: + * 1- any parameters (delimited with a ';') will be stripped. + * 2- if a charset parameter is given, then its value will replace the + * the contentCharset attribute of the channel. + * 3- the stripped contentType will be lowercased. + * Any implementation of nsIChannel must follow these rules. + */ + attribute ACString contentType; + + /** + * The character set of the channel's content if available and if applicable. + * This attribute only applies to textual data. + * + * The value of the contentCharset attribute is a mixedcase string. + */ + attribute ACString contentCharset; + + /** + * The length of the data associated with the channel if available. A value + * of -1 indicates that the content length is unknown. Note that this is a + * 64-bit value and obsoletes the "content-length" property used on some + * channels. + */ + attribute int64_t contentLength; + + /** + * Synchronously open the channel. + * + * @return blocking input stream to the channel's data. + * + * NOTE: nsIChannel implementations are not required to implement this + * method. Moreover, since this method may block the calling thread, it + * should not be called on a thread that processes UI events. Like any + * other nsIChannel method it must not be called on any thread other + * than the XPCOM main thread. + * + * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel + * is reopened. + */ + nsIInputStream open(); + + /** + * Performs content security check and calls open() + */ + nsIInputStream open2(); + + /** + * Asynchronously open this channel. Data is fed to the specified stream + * listener as it becomes available. The stream listener's methods are + * called on the thread that calls asyncOpen and are not called until + * after asyncOpen returns. If asyncOpen returns successfully, the + * channel promises to call at least onStartRequest and onStopRequest. + * + * If the nsIRequest object passed to the stream listener's methods is not + * this channel, an appropriate onChannelRedirect notification needs to be + * sent to the notification callbacks before onStartRequest is called. + * Once onStartRequest is called, all following method calls on aListener + * will get the request that was passed to onStartRequest. + * + * If the channel's and loadgroup's notification callbacks do not provide + * an nsIChannelEventSink when onChannelRedirect would be called, that's + * equivalent to having called onChannelRedirect. + * + * If asyncOpen returns successfully, the channel is responsible for + * keeping itself alive until it has called onStopRequest on aListener or + * called onChannelRedirect. + * + * Implementations are allowed to synchronously add themselves to the + * associated load group (if any). + * + * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the + * channel is reopened. + * + * @param aListener the nsIStreamListener implementation + * @param aContext an opaque parameter forwarded to aListener's methods + * @see nsIChannelEventSink for onChannelRedirect + */ + void asyncOpen(in nsIStreamListener aListener, in nsISupports aContext); + + /** + * Performs content security check and calls asyncOpen(). + */ + void asyncOpen2(in nsIStreamListener aListener); + + /************************************************************************** + * Channel specific load flags: + * + * Bits 26-31 are reserved for future use by this interface or one of its + * derivatives (e.g., see nsICachingChannel). + */ + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to a document URI. + */ + const unsigned long LOAD_DOCUMENT_URI = 1 << 16; + + /** + * If the end consumer for this load has been retargeted after discovering + * its content, this flag will be set: + */ + const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17; + + /** + * This flag is set to indicate that this channel is replacing another + * channel. This means that: + * + * 1) the stream listener this channel will be notifying was initially + * passed to the asyncOpen method of some other channel + * + * and + * + * 2) this channel's URI is a better identifier of the resource being + * accessed than this channel's originalURI. + * + * This flag can be set, for example, for redirects or for cases when a + * single channel has multiple parts to it (and thus can follow + * onStopRequest with another onStartRequest/onStopRequest pair, each pair + * for a different request). + */ + const unsigned long LOAD_REPLACE = 1 << 18; + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to an initial document URI load (e.g., link click). + */ + const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19; + + /** + * Set (e.g., by the URILoader) to indicate whether or not the end consumer + * for this load has been determined. + */ + const unsigned long LOAD_TARGETED = 1 << 20; + + /** + * If this flag is set, the channel should call the content sniffers as + * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY. + * + * Note: Channels may ignore this flag; however, new channel implementations + * should only do so with good reason. + */ + const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21; + + /** + * This flag tells the channel to use URI classifier service to check + * the URI when opening the channel. + */ + const unsigned long LOAD_CLASSIFY_URI = 1 << 22; + + /** + * If this flag is set, the media-type content sniffer will be allowed + * to override any server-set content-type. Otherwise it will only + * be allowed to override "no content type" and application/octet-stream. + */ + const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23; + + /** + * Set to let explicitely provided credentials be used over credentials + * we have cached previously. In some situations like form login using HTTP + * auth via XMLHttpRequest we need to let consumers override the cached + * credentials explicitely. For form login 403 response instead of 401 is + * usually used to prevent an auth dialog. But any code other then 401/7 + * will leave original credentials in the cache and there is then no way + * to override them for the same user name. + */ + const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24; + + /** + * Set to force bypass of any service worker interception of the channel. + */ + const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25; + + // nsICachingChannel load flags begin at bit 26. + + /** + * Access to the type implied or stated by the Content-Disposition header + * if available and if applicable. This allows determining inline versus + * attachment. + * + * Setting contentDisposition provides a hint to the channel about the + * disposition. If a normal Content-Disposition header is present its + * value will always be used. If it is missing the hinted value will + * be used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty, and return + * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present. + */ + attribute unsigned long contentDisposition; + const unsigned long DISPOSITION_INLINE = 0; + const unsigned long DISPOSITION_ATTACHMENT = 1; + + /** + * Access to the filename portion of the Content-Disposition header if + * available and if applicable. This allows getting the preferred filename + * without having to parse it out yourself. + * + * Setting contentDispositionFilename provides a hint to the channel about + * the disposition. If a normal Content-Disposition header is present its + * value will always be used. If it is missing the hinted value will be + * used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't + * exist for this type of channel, if the header is empty, if the header + * doesn't contain a filename portion, or the value of the filename + * attribute is empty/missing. + */ + attribute AString contentDispositionFilename; + + /** + * Access to the raw Content-Disposition header if available and applicable. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty. + * + * @deprecated Use contentDisposition/contentDispositionFilename instead. + */ + readonly attribute ACString contentDispositionHeader; + + /** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ + attribute nsILoadInfo loadInfo; + +%{ C++ + inline already_AddRefed GetLoadInfo() + { + nsCOMPtr result; + mozilla::DebugOnly rv = GetLoadInfo(getter_AddRefs(result)); + MOZ_ASSERT(NS_SUCCEEDED(rv) || !result); + return result.forget(); + } +%} + +}; diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl new file mode 100644 index 000000000..64c5b9eec --- /dev/null +++ b/netwerk/base/nsIChannelEventSink.idl @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIAsyncVerifyRedirectCallback; + +/** + * Implement this interface to receive control over various channel events. + * Channels will try to get this interface from a channel's + * notificationCallbacks or, if not available there, from the loadGroup's + * notificationCallbacks. + * + * These methods are called before onStartRequest. + */ +[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)] +interface nsIChannelEventSink : nsISupports +{ + /** + * This is a temporary redirect. New requests for this resource should + * continue to use the URI of the old channel. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_TEMPORARY = 1 << 0; + + /** + * This is a permanent redirect. New requests for this resource should use + * the URI of the new channel (This might be an HTTP 301 reponse). + * If this flag is not set, this is a temporary redirect. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_PERMANENT = 1 << 1; + + /** + * This is an internal redirect, i.e. it was not initiated by the remote + * server, but is specific to the channel implementation. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_INTERNAL = 1 << 2; + + /** + * This is a special-cased redirect coming from hitting HSTS upgrade + * redirect from http to https only. In some cases this type of redirect + * may be considered as safe despite not being the-same-origin redirect. + */ + const unsigned long REDIRECT_STS_UPGRADE = 1 << 3; + + /** + * Called when a redirect occurs. This may happen due to an HTTP 3xx status + * code. The purpose of this method is to notify the sink that a redirect + * is about to happen, but also to give the sink the right to veto the + * redirect by throwing or passing a failure-code in the callback. + * + * Note that vetoing the redirect simply means that |newChannel| will not + * be opened. It is important to understand that |oldChannel| will continue + * loading as if it received a HTTP 200, which includes notifying observers + * and possibly display or process content attached to the HTTP response. + * If the sink wants to prevent this loading it must explicitly deal with + * it, e.g. by calling |oldChannel->Cancel()| + * + * There is a certain freedom in implementing this method: + * + * If the return-value indicates success, a callback on |callback| is + * required. This callback can be done from within asyncOnChannelRedirect + * (effectively making the call synchronous) or at some point later + * (making the call asynchronous). Repeat: A callback must be done + * if this method returns successfully. + * + * If the return value indicates error (method throws an exception) + * the redirect is vetoed and no callback must be done. Repeat: No + * callback must be done if this method throws! + * + * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback() + * + * @param oldChannel + * The channel that's being redirected. + * @param newChannel + * The new channel. This channel is not opened yet. + * @param flags + * Flags indicating the type of redirect. A bitmask consisting + * of flags from above. + * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be + * set. + * @param callback + * Object to inform about the async result of this method + * + * @throw Throwing an exception will cause the redirect to be + * cancelled + */ + void asyncOnChannelRedirect(in nsIChannel oldChannel, + in nsIChannel newChannel, + in unsigned long flags, + in nsIAsyncVerifyRedirectCallback callback); +}; diff --git a/netwerk/base/nsIChannelWithDivertableParentListener.idl b/netwerk/base/nsIChannelWithDivertableParentListener.idl new file mode 100644 index 000000000..1fb52931f --- /dev/null +++ b/netwerk/base/nsIChannelWithDivertableParentListener.idl @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +namespace mozilla { +namespace net { +class ADivertableParentChannel; +} +} +%} + +[ptr] native ADivertableParentChannelPtr(mozilla::net::ADivertableParentChannel); + +/** When we are diverting messages from the child to the parent. The + * nsHttpChannel and nsFtpChannel must know that there is a ChannelParent to + * be able to suspend message delivery if the channel is suspended. + */ +[uuid(c073d79f-2503-4dff-ba87-d3071f8b433b)] +interface nsIChannelWithDivertableParentListener : nsISupports +{ + /** + * Informs nsHttpChannel or nsFtpChannel that a ParentChannel starts + * diverting messages. During this time all suspend/resume calls to the + * channel must also suspend the ParentChannel by calling + * SuspendMessageDiversion/ResumeMessageDiversion. + */ + void MessageDiversionStarted(in ADivertableParentChannelPtr aParentChannel); + + /** + * The message diversion has finished the calls to + * SuspendMessageDiversion/ResumeMessageDiversion are not necessary anymore. + */ + void MessageDiversionStop(); + + /** + * Internal versions of Suspend/Resume that suspend (or resume) the channel + * but do not suspend the ParentChannel's IPDL message queue. + */ + void SuspendInternal(); + void ResumeInternal(); +}; diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl new file mode 100644 index 000000000..fae77f322 --- /dev/null +++ b/netwerk/base/nsIChildChannel.idl @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStreamListener; + +/** + * Implemented by content side of IPC protocols. + */ + +[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)] +interface nsIChildChannel : nsISupports +{ + /** + * Create the chrome side of the IPC protocol and join an existing 'real' + * channel on the parent process. The id is provided by + * nsIRedirectChannelRegistrar on the chrome process and pushed to the child + * protocol as an argument to event starting a redirect. + * + * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created + * child channel, where the new channel is intended to be created on the + * child process. + */ + void connectParent(in uint32_t registrarId); + + /** + * As AsyncOpen is called on the chrome process for redirect target channels, + * we have to inform the child side of the protocol of that fact by a special + * method. + */ + void completeRedirectSetup(in nsIStreamListener aListener, + in nsISupports aContext); +}; diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl new file mode 100644 index 000000000..30590b324 --- /dev/null +++ b/netwerk/base/nsIClassOfService.idl @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIClassOfService.idl + * + * Used to express class dependencies and characteristics - complimentary to + * nsISupportsPriority which is used to express weight + * + * Channels that implement this interface may make use of this + * information in different ways. + * + * The default gecko HTTP/1 stack makes Followers wait for Leaders to + * complete before dispatching followers. Other classes run in + * parallel - neither being blocked nor blocking. All grouping is done + * based on the Load Group - separate load groups proceed + * independently. + * + * HTTP/2 does not use the load group, but prioritization is done per + * HTTP/2 session. HTTP/2 dispatches all the requests as soon as + * possible. + * The various classes are assigned logical priority + * dependency groups and then transactions of that class depend on the + * group. In this model Followers block on Leaders and Speculative + * depends on Background. See Http2Stream.cpp for weighting details. + * + */ + +[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)] +interface nsIClassOfService : nsISupports +{ + attribute unsigned long classFlags; + + void clearClassFlags(in unsigned long flags); + void addClassFlags(in unsigned long flags); + + const unsigned long Leader = 1 << 0; + const unsigned long Follower = 1 << 1; + const unsigned long Speculative = 1 << 2; + const unsigned long Background = 1 << 3; + const unsigned long Unblocked = 1 << 4; + const unsigned long Throttleable = 1 << 5; + const unsigned long UrgentStart = 1 << 6; +}; diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl new file mode 100644 index 000000000..f9052b8e6 --- /dev/null +++ b/netwerk/base/nsIContentSniffer.idl @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRequest; + +/** + * Content sniffer interface. Components implementing this interface can + * determine a MIME type from a chunk of bytes. + */ +[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)] +interface nsIContentSniffer : nsISupports +{ + /** + * Given a chunk of data, determines a MIME type. Information from the given + * request may be used in order to make a better decision. + * + * @param aRequest The request where this data came from. May be null. + * @param aData Data to check + * @param aLength Length of the data + * + * @return The content type + * + * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined. + * + * @note Implementations should consider the request read-only. Especially, + * they should not attempt to set the content type property that subclasses of + * nsIRequest might offer. + */ + ACString getMIMETypeFromContent(in nsIRequest aRequest, + [const,array,size_is(aLength)] in octet aData, + in unsigned long aLength); +}; diff --git a/netwerk/base/nsICryptoFIPSInfo.idl b/netwerk/base/nsICryptoFIPSInfo.idl new file mode 100644 index 000000000..1defc56ab --- /dev/null +++ b/netwerk/base/nsICryptoFIPSInfo.idl @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(99e81922-7318-4431-b3aa-78b3cb4119bb)] +interface nsICryptoFIPSInfo : nsISupports +{ + readonly attribute boolean isFIPSModeActive; +}; + +%{C++ +#define NS_CRYPTO_FIPSINFO_SERVICE_CONTRACTID "@mozilla.org/crypto/fips-info-service;1" +%} diff --git a/netwerk/base/nsICryptoHMAC.idl b/netwerk/base/nsICryptoHMAC.idl new file mode 100644 index 000000000..92e06a0a4 --- /dev/null +++ b/netwerk/base/nsICryptoHMAC.idl @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIInputStream; +interface nsIKeyObject; + +/** + * nsICryptoHMAC + * This interface provides HMAC signature algorithms. + */ + +[scriptable, uuid(8FEB4C7C-1641-4a7b-BC6D-1964E2099497)] +interface nsICryptoHMAC : nsISupports +{ + /** + * Hashing Algorithms. These values are to be used by the + * |init| method to indicate which hashing function to + * use. These values map onto the values defined in + * mozilla/security/nss/lib/softoken/pkcs11t.h and are + * switched to CKM_*_HMAC constant. + */ + const short MD2 = 1; + const short MD5 = 2; + const short SHA1 = 3; + const short SHA256 = 4; + const short SHA384 = 5; + const short SHA512 = 6; + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * This value must be one of the above valid + * algorithm types. + * + * @param aKeyObject + * Object holding a key. To create the key object use for instance: + * var keyObject = Components.classes["@mozilla.org/security/keyobjectfactory;1"] + * .getService(Components.interfaces.nsIKeyObjectFactory) + * .keyFromString(Components.interfaces.nsIKeyObject.HMAC, rawKeyData); + * + * WARNING: This approach is not FIPS compliant. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method must be called before any other method + * on this interface is called. + */ + void init(in unsigned long aAlgorithm, in nsIKeyObject aKeyObject); + + /** + * @param aData a buffer to calculate the hash over + * + * @param aLen the length of the buffer |aData| + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + */ + void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen); + + /** + * Calculates and updates a new hash based on a given data stream. + * + * @param aStream an input stream to read from. + * + * @param aLen how much to read from the given |aStream|. Passing + * UINT32_MAX indicates that all data available will be used + * to update the hash. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of + * data to be calculated into the hash is not available. + * + */ + void updateFromStream(in nsIInputStream aStream, in unsigned long aLen); + + /** + * Completes this HMAC object and produces the actual HMAC diegest data. + * + * @param aASCII if true then the returned value is a base-64 + * encoded string. if false, then the returned value is + * binary data. + * + * @return a hash of the data that was read by this object. This can + * be either binary data or base 64 encoded. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * NOTE: This method may be called any time after |init| + * is called. This call resets the object to its + * pre-init state. + */ + ACString finish(in boolean aASCII); + + /** + * Reinitialize HMAC context to be reused with the same + * settings (the key and hash algorithm) but on different + * set of data. + */ + void reset(); +}; diff --git a/netwerk/base/nsICryptoHash.idl b/netwerk/base/nsICryptoHash.idl new file mode 100644 index 000000000..cd865a3a9 --- /dev/null +++ b/netwerk/base/nsICryptoHash.idl @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIInputStream; + +/** + * nsICryptoHash + * This interface provides crytographic hashing algorithms. + */ + +[scriptable, uuid(1e5b7c43-4688-45ce-92e1-77ed931e3bbe)] +interface nsICryptoHash : nsISupports +{ + /** + * Hashing Algorithms. These values are to be used by the + * |init| method to indicate which hashing function to + * use. These values map directly onto the values defined + * in mozilla/security/nss/lib/cryptohi/hasht.h. + */ + const short MD2 = 1; /* String value: "md2" */ + const short MD5 = 2; /* String value: "md5" */ + const short SHA1 = 3; /* String value: "sha1" */ + const short SHA256 = 4; /* String value: "sha256" */ + const short SHA384 = 5; /* String value: "sha384" */ + const short SHA512 = 6; /* String value: "sha512" */ + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * This value must be one of the above valid + * algorithm types. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method or initWithString must be called + * before any other method on this interface is called. + */ + void init(in unsigned long aAlgorithm); + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method or init must be called before any + * other method on this interface is called. + */ + void initWithString(in ACString aAlgorithm); + + /** + * @param aData a buffer to calculate the hash over + * + * @param aLen the length of the buffer |aData| + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + */ + void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen); + + /** + * Calculates and updates a new hash based on a given data stream. + * + * @param aStream an input stream to read from. + * + * @param aLen how much to read from the given |aStream|. Passing + * UINT32_MAX indicates that all data available will be used + * to update the hash. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of + * data to be calculated into the hash is not available. + * + */ + void updateFromStream(in nsIInputStream aStream, in unsigned long aLen); + + /** + * Completes this hash object and produces the actual hash data. + * + * @param aASCII if true then the returned value is a base-64 + * encoded string. if false, then the returned value is + * binary data. + * + * @return a hash of the data that was read by this object. This can + * be either binary data or base 64 encoded. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * NOTE: This method may be called any time after |init| + * is called. This call resets the object to its + * pre-init state. + */ + ACString finish(in boolean aASCII); +}; diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl new file mode 100644 index 000000000..a18710e5a --- /dev/null +++ b/netwerk/base/nsIDashboard.idl @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* A JavaScript callback function that takes a JSON as its parameter. + * The returned JSON contains arrays with data + */ +[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)] +interface NetDashboardCallback : nsISupports +{ + void onDashboardDataAvailable(in jsval data); +}; + +/* The dashboard service. + * The async API returns JSONs, which hold arrays with the required info. + * Only one request of each type may be pending at any time. + */ +[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)] +interface nsIDashboard : nsISupports +{ + /* Arrays: host, port, tcp, active, socksent, sockreceived + * Values: sent, received */ + void requestSockets(in NetDashboardCallback cb); + + /* Arrays: host, port, spdy, ssl + * Array of arrays: active, idle */ + void requestHttpConnections(in NetDashboardCallback cb); + + /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */ + void requestWebsocketConnections(in NetDashboardCallback cb); + + /* Arrays: hostname, family, hostaddr, expiration */ + void requestDNSInfo(in NetDashboardCallback cb); + + /* aProtocol: a transport layer protocol: + * ex: "ssl", "tcp", default is "tcp". + * aHost: the host's name + * aPort: the port which the connection will open on + * aTimeout: the timespan before the connection will be timed out */ + void requestConnection(in ACString aHost, in unsigned long aPort, + in string aProtocol, in unsigned long aTimeout, + in NetDashboardCallback cb); + + /* When true, the service will log websocket events */ + attribute boolean enableLogging; + + /* DNS resolver for host name + * aHost: host name */ + void requestDNSLookup(in ACString aHost, in NetDashboardCallback cb); + + AUTF8String getLogPath(); +}; diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl new file mode 100644 index 000000000..d84fd2ed4 --- /dev/null +++ b/netwerk/base/nsIDashboardEventNotifier.idl @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[builtinclass, uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)] +interface nsIDashboardEventNotifier : nsISupports +{ + /* These methods are called to register a websocket event with the dashboard + * + * A host is identified by the (aHost, aSerial) pair. + * aHost: the host's name: example.com + * aSerial: a number that uniquely identifies the websocket + * + * aEncrypted: if the connection is encrypted + * aLength: the length of the message in bytes + */ + void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted); + void removeHost(in ACString aHost, in unsigned long aSerial); + void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); + void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); +}; diff --git a/netwerk/base/nsIDeprecationWarner.idl b/netwerk/base/nsIDeprecationWarner.idl new file mode 100644 index 000000000..72303b815 --- /dev/null +++ b/netwerk/base/nsIDeprecationWarner.idl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Interface for warning about deprecated operations. Consumers should + * attach this interface to the channel's notification callbacks/loadgroup. + */ +[uuid(665c5124-2c52-41ba-ae72-2393f8e76c25)] +interface nsIDeprecationWarner : nsISupports +{ + /** + * Issue a deprecation warning. + * + * @param aWarning a warning code as declared in nsDeprecatedOperationList.h. + * @param aAsError optional boolean flag indicating whether the warning + * should be treated as an error. + */ + void issueWarning(in uint32_t aWarning, [optional] in bool aAsError); +}; diff --git a/netwerk/base/nsIDivertableChannel.idl b/netwerk/base/nsIDivertableChannel.idl new file mode 100644 index 000000000..4c28ca7dc --- /dev/null +++ b/netwerk/base/nsIDivertableChannel.idl @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +namespace mozilla { +namespace net { +class ChannelDiverterChild; +} +} +%} + +[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild); + +interface nsIStreamListener; + +/** + * A channel implementing this interface allows diverting from an + * nsIStreamListener in the child process to one in the parent. + */ +[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)] +interface nsIDivertableChannel : nsISupports +{ + /** + * CHILD ONLY. + * Called by Necko client in child process during OnStartRequest to divert + * nsIStreamListener and nsIRequest callbacks to the parent process. + * + * The process should look like the following: + * + * 1) divertToParent is called in the child process. It can only be called + * during OnStartRequest(). + * + * 2) The ChannelDiverterChild that is returned is an IPDL object. It should + * be passed via some other IPDL method of the client's choosing to the + * parent. On the parent the ChannelDiverterParent's divertTo() function + * should be called with an nsIStreamListener that will then receive the + * OnStartRequest/OnDataAvailable/OnStopRequest for the channel. The + * ChannelDiverterParent can then be deleted (which will also destroy the + * ChannelDiverterChild in the child). + * + * After divertToParent() has been called, NO further function calls + * should be made on the channel. It is a dead object for all purposes. + * The reference that the channel holds to the listener in the child is + * released is once OnStartRequest completes, and no other + * nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made + * to it. + * + * @return ChannelDiverterChild IPDL actor to be passed to parent process by + * client IPDL message, e.g. PClient.DivertUsing(PDiverterChild). + * + * @throws exception if the channel was canceled early. Throws status code of + * canceled channel. + */ + ChannelDiverterChild divertToParent(); + + /** + * nsUnknownDecoder delays calling OnStartRequest until it gets enough data + * to decide about the content type (until OnDataAvaiable is called). In a + * OnStartRequest DivertToParent can be called but some OnDataAvailables are + * already called and therefore can not be diverted to parent. + * + * nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its + * OnStartRequest function and when it calls OnStartRequest of the next + * listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this + * function Child process will decide to discarge data if it is not diverting + * to parent or keep them if it is diverting to parent. + */ + void unknownDecoderInvolvedKeepData(); + + void unknownDecoderInvolvedOnStartRequestCalled(); + + readonly attribute bool divertingToParent; +}; diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl new file mode 100644 index 000000000..738940b4b --- /dev/null +++ b/netwerk/base/nsIDownloader.idl @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +interface nsIFile; +interface nsIDownloadObserver; + +/** + * nsIDownloader + * + * A downloader is a special implementation of a nsIStreamListener that will + * make the contents of the stream available as a file. This may utilize the + * disk cache as an optimization to avoid an extra copy of the data on disk. + * The resulting file is valid from the time the downloader completes until + * the last reference to the downloader is released. + */ +[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)] +interface nsIDownloader : nsIStreamListener +{ + /** + * Initialize this downloader + * + * @param observer + * the observer to be notified when the download completes. + * @param downloadLocation + * the location where the stream contents should be written. + * if null, the downloader will select a location and the + * resulting file will be deleted (or otherwise made invalid) + * when the downloader object is destroyed. if an explicit + * download location is specified then the resulting file will + * not be deleted, and it will be the callers responsibility + * to keep track of the file, etc. + */ + void init(in nsIDownloadObserver observer, + in nsIFile downloadLocation); +}; + +[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)] +interface nsIDownloadObserver : nsISupports +{ + /** + * Called to signal a download that has completed. + */ + void onDownloadComplete(in nsIDownloader downloader, + in nsIRequest request, + in nsISupports ctxt, + in nsresult status, + in nsIFile result); +}; diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl new file mode 100644 index 000000000..77cee0d28 --- /dev/null +++ b/netwerk/base/nsIEncodedChannel.idl @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIUTF8StringEnumerator; +interface nsIStreamListener; +interface nsISupports; + +/** + * A channel interface which allows special handling of encoded content + */ + +[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)] +interface nsIEncodedChannel : nsISupports +{ + /** + * This attribute holds the MIME types corresponding to the content + * encodings on the channel. The enumerator returns nsISupportsCString + * objects. The first one corresponds to the outermost encoding on the + * channel and then we work our way inward. "identity" is skipped and not + * represented on the list. Unknown encodings make the enumeration stop. + * If you want the actual Content-Encoding value, use + * getResponseHeader("Content-Encoding"). + * + * When there is no Content-Encoding header, this property is null. + * + * Modifying the Content-Encoding header on the channel will cause + * this enumerator to have undefined behavior. Don't do it. + * + * Also note that contentEncodings only exist during or after OnStartRequest. + * Calling contentEncodings before OnStartRequest is an error. + */ + readonly attribute nsIUTF8StringEnumerator contentEncodings; + + /** + * This attribute controls whether or not content conversion should be + * done per the Content-Encoding response header. applyConversion can only + * be set before or during OnStartRequest. Calling this during + * OnDataAvailable is an error. + * + * TRUE by default. + */ + attribute boolean applyConversion; + + /** + * This function will start converters if they are available. + * aNewNextListener will be nullptr if no converter is available. + */ + void doApplyContentConversions(in nsIStreamListener aNextListener, + out nsIStreamListener aNewNextListener, + in nsISupports aCtxt); +}; diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl new file mode 100644 index 000000000..6f86dbb05 --- /dev/null +++ b/netwerk/base/nsIExternalProtocolHandler.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProtocolHandler.idl" + +[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)] +interface nsIExternalProtocolHandler: nsIProtocolHandler +{ + /** + * This method checks if the external handler exists for a given scheme. + * + * @param scheme external scheme. + * @return TRUE if the external handler exists for the input scheme, FALSE otherwise. + */ + boolean externalAppExistsForScheme(in ACString scheme); +}; diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl new file mode 100644 index 000000000..e5f357f6a --- /dev/null +++ b/netwerk/base/nsIFileStreams.idl @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" + +interface nsIFile; + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); + +/** + * An input stream that allows you to read from a file. + */ +[scriptable, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)] +interface nsIFileInputStream : nsIInputStream +{ + /** + * @param file file to read from + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDONLY). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * If this is set, the file will be deleted by the time the stream is + * closed. It may be removed before the stream is closed if it is possible + * to delete it and still read from it. + * + * If OPEN_ON_READ is defined, and the file was recreated after the first + * delete, the file will be deleted again when it is closed again. + */ + const long DELETE_ON_CLOSE = 1<<1; + + /** + * If this is set, the file will close automatically when the end of the + * file is reached. + */ + const long CLOSE_ON_EOF = 1<<2; + + /** + * If this is set, the file will be reopened whenever we reach the start of + * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative + * seek that happen to reach the beginning of the file. If the file is + * already open and the seek occurs, it will happen naturally. (The file + * will only be reopened if it is closed for some reason.) + */ + const long REOPEN_ON_REWIND = 1<<3; + + /** + * If this is set, the file will be opened (i.e., a call to + * PR_Open done) only when we do an actual operation on the stream, + * or more specifically, when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - ReadLine + * + * DEFER_OPEN is useful if we use the stream on a background + * thread, so that the opening and possible |stat|ing of the file + * happens there as well. + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read. Also, the file is not locked when Init is called, + * so it might be deleted before we try to read from it. + */ + const long DEFER_OPEN = 1<<4; + + /** + * This flag has no effect and is totally ignored on any platform except + * Windows since this is the default behavior on POSIX systems. On Windows + * if this flag is set then the stream is opened in a special mode that + * allows the OS to delete the file from disk just like POSIX. + */ + const long SHARE_DELETE = 1<<5; +}; + +/** + * An output stream that lets you stream to a file. + */ +[scriptable, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)] +interface nsIFileOutputStream : nsIOutputStream +{ + /** + * @param file file to write to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_WRONLY | + * PR_CREATE_FILE | PR_TRUNCATE) + * @param perm file mode bits listed in prio.h or -1 to + * use the default permissions (0664) + * @param behaviorFlags flags specifying various behaviors of the class + * (currently none supported) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * @param length asks the operating system to allocate storage for + * this file of at least |length| bytes long, and + * set the file length to the corresponding size. + * @throws NS_ERROR_FAILURE if the preallocation fails. + * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened. + */ + [noscript] void preallocate(in long long length); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Write + * - Flush + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first write, and if the file is to be created, then it will not + * appear on the disk until the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * An input stream that allows you to read from a slice of a file. + */ +[scriptable, uuid(3ce03a2f-97f7-4375-b6bb-1788a60cad3b)] +interface nsIPartialFileInputStream : nsISupports +{ + /** + * Initialize with a file and new start/end positions. Both start and + * start+length must be smaller than the size of the file. Not doing so + * will lead to undefined behavior. + * You must initialize the stream, and only initialize it once, before it + * can be used. + * + * @param file file to read from + * @param start start offset of slice to read. Must be smaller + * than the size of the file. + * @param length length of slice to read. Must be small enough that + * start+length is smaller than the size of the file. + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDONLY). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in nsIFileInputStream) + */ + void init(in nsIFile file, in unsigned long long start, + in unsigned long long length, + in long ioFlags, in long perm, in long behaviorFlags); +}; + +/** + * A stream that allows you to read from a file or stream to a file. + */ +[scriptable, uuid(82cf605a-8393-4550-83ab-43cd5578e006)] +interface nsIFileStream : nsISupports +{ + /** + * @param file file to read from or stream to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDWR). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - Flush + * - Write + * - GetSize + * - GetLastModified + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read or write. The file is not locked when Init is called, + * so it might be deleted before we try to read from it and if the + * file is to be created, then it will not appear on the disk until + * the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * An interface that allows you to get some metadata like file size and + * file last modified time. + */ +[scriptable, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)] +interface nsIFileMetadata : nsISupports +{ + /** + * File size in bytes; + */ + readonly attribute long long size; + + /** + * File last modified time in milliseconds from midnight (00:00:00), + * January 1, 1970 Greenwich Mean Time (GMT). + */ + readonly attribute long long lastModified; + + /** + * The internal file descriptor. It can be used for memory mapping of the + * underlying file. Please use carefully! + */ + [noscript] PRFileDescPtr getFileDescriptor(); +}; diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl new file mode 100644 index 000000000..9f8b67cb8 --- /dev/null +++ b/netwerk/base/nsIFileURL.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURL.idl" + +interface nsIFile; + +/** + * nsIFileURL provides access to the underlying nsIFile object corresponding to + * an URL. The URL scheme need not be file:, since other local protocols may + * map URLs to files (e.g., resource:). + */ +[scriptable, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)] +interface nsIFileURL : nsIURL +{ + /** + * Get/Set nsIFile corresponding to this URL. + * + * - Getter returns a reference to an immutable object. Callers must clone + * before attempting to modify the returned nsIFile object. NOTE: this + * constraint might not be enforced at runtime, so beware!! + * + * - Setter clones the nsIFile object (allowing the caller to safely modify + * the nsIFile object after setting it on this interface). + */ + attribute nsIFile file; +}; diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl new file mode 100644 index 000000000..a4d6ef894 --- /dev/null +++ b/netwerk/base/nsIForcePendingChannel.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIForcePending interface exposes a function that enables overwriting of the normal + * behavior for the channel's IsPending(), forcing 'true' to be returned. + */ + +[noscript, uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)] +interface nsIForcePendingChannel : nsISupports +{ + +/** + * forcePending(true) overrides the normal behavior for the + * channel's IsPending(), forcing 'true' to be returned. A call to + * forcePending(false) reverts IsPending() back to normal behavior. + */ + void forcePending(in boolean aForcePending); +}; diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl new file mode 100644 index 000000000..870886390 --- /dev/null +++ b/netwerk/base/nsIFormPOSTActionChannel.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIUploadChannel.idl" + +/** + * nsIFormPOSTActionChannel + * + * Channel classes that want to be allowed for HTML form POST action must + * implement this interface. + */ +[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)] +interface nsIFormPOSTActionChannel : nsIUploadChannel +{ +}; diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl new file mode 100644 index 000000000..9ce233515 --- /dev/null +++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)] +interface nsIHttpAuthenticatorCallback : nsISupports +{ + /** + * Authentication data for a header is available. + * + * @param aCreds + * Credentials which were obtained asynchonously. + * @param aFlags + * Flags set by asynchronous call. + * @param aResult + * Result status of credentials generation + * @param aSessionState + * Modified session state to be passed to caller + * @param aContinuationState + * Modified continuation state to be passed to caller + */ + void onCredsGenerated(in string aCreds, + in unsigned long aFlags, + in nsresult aResult, + in nsISupports aSessionsState, + in nsISupports aContinuationState); + +}; + diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl new file mode 100644 index 000000000..f44605c00 --- /dev/null +++ b/netwerk/base/nsIHttpPushListener.idl @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIHttpChannel; + +/** + * nsIHttpPushListener + * + * Used for triggering when a HTTP/2 push is received. + * + */ +[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)] +interface nsIHttpPushListener : nsISupports +{ + /** + * When provided as a notificationCallback to an httpChannel, this.onPush() + * will be invoked when there is a >= Http2 push to that + * channel. The push may be in progress. + * + * The consumer must start the new channel in the usual way by calling + * pushChannel.AsyncOpen with a nsIStreamListener object that + * will receive the normal sequence of OnStartRequest(), + * 0 to N OnDataAvailable(), and onStopRequest(). + * + * The new channel can be canceled after the AsyncOpen if it is not wanted. + * + * @param associatedChannel + * the monitor channel that was recieved on + * @param pushChannel + * a channel to the resource which is being pushed + */ + void onPush(in nsIHttpChannel associatedChannel, + in nsIHttpChannel pushChannel); +}; diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl new file mode 100644 index 000000000..9bb777405 --- /dev/null +++ b/netwerk/base/nsIIOService.idl @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIProtocolHandler; +interface nsIChannel; +interface nsIURI; +interface nsIFile; +interface nsIDOMNode; +interface nsIPrincipal; +interface nsILoadInfo; + +/** + * nsIIOService provides a set of network utility functions. This interface + * duplicates many of the nsIProtocolHandler methods in a protocol handler + * independent way (e.g., NewURI inspects the scheme in order to delegate + * creation of the new URI to the appropriate protocol handler). nsIIOService + * also provides a set of URL parsing utility functions. These are provided + * as a convenience to the programmer and in some cases to improve performance + * by eliminating intermediate data structures and interfaces. + */ +[scriptable, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)] +interface nsIIOService : nsISupports +{ + /** + * Returns a protocol handler for a given URI scheme. + * + * @param aScheme the URI scheme + * @return reference to corresponding nsIProtocolHandler + */ + nsIProtocolHandler getProtocolHandler(in string aScheme); + + /** + * Returns the protocol flags for a given scheme. + * + * @param aScheme the URI scheme + * @return value of corresponding nsIProtocolHandler::protocolFlags + */ + unsigned long getProtocolFlags(in string aScheme); + + /** + * This method constructs a new URI by determining the scheme of the + * URI spec, and then delegating the construction of the URI to the + * protocol handler for that scheme. QueryInterface can be used on + * the resulting URI object to obtain a more specific type of URI. + * + * @see nsIProtocolHandler::newURI + */ + nsIURI newURI(in AUTF8String aSpec, + [optional] in string aOriginCharset, + [optional] in nsIURI aBaseURI); + + /** + * This method constructs a new URI from a nsIFile. + * + * @param aFile specifies the file path + * @return reference to a new nsIURI object + * + * Note: in the future, for perf reasons we should allow + * callers to specify whether this is a file or directory by + * splitting this into newDirURI() and newActualFileURI(). + */ + nsIURI newFileURI(in nsIFile aFile); + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + */ + nsIChannel newChannelFromURI2(in nsIURI aURI, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...) + */ + nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, + in nsILoadInfo aLoadInfo); + + /** + * Equivalent to newChannelFromURI2(newURI(...)) + */ + nsIChannel newChannel2(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * ***** DEPRECATED ***** + * Please use NewChannelFromURI2() + * + * Creates a channel for a given URI. + * + * @param aURI nsIURI from which to make a channel + * @return reference to the new nsIChannel object + */ + nsIChannel newChannelFromURI(in nsIURI aURI); + + /** + * ***** DEPRECATED ***** + * Please use newChannel2(). + * + * Equivalent to newChannelFromURI(newURI(...)) + */ + nsIChannel newChannel(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI); + + + /** + * Returns true if networking is in "offline" mode. When in offline mode, + * attempts to access the network will fail (although this does not + * necessarily correlate with whether there is actually a network + * available -- that's hard to detect without causing the dialer to + * come up). + * + * Changing this fires observer notifications ... see below. + */ + attribute boolean offline; + + /** + * Returns false if there are no interfaces for a network request + */ + readonly attribute boolean connectivity; + + /** + * Checks if a port number is banned. This involves consulting a list of + * unsafe ports, corresponding to network services that may be easily + * exploitable. If the given port is considered unsafe, then the protocol + * handler (corresponding to aScheme) will be asked whether it wishes to + * override the IO service's decision to block the port. This gives the + * protocol handler ultimate control over its own security policy while + * ensuring reasonable, default protection. + * + * @see nsIProtocolHandler::allowPort + */ + boolean allowPort(in long aPort, in string aScheme); + + /** + * Utility to extract the scheme from a URL string, consistently and + * according to spec (see RFC 2396). + * + * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme + * can also be extracted from a URL string via nsIURI. This method + * is provided purely as an optimization. + * + * @param aSpec the URL string to parse + * @return URL scheme + * + * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form. + */ + ACString extractScheme(in AUTF8String urlString); +}; + +%{C++ +/** + * We send notifications through nsIObserverService with topic + * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE + * when 'offline' has changed from false to true, and we are about + * to shut down network services such as DNS. When those + * services have been shut down, we send a notification with + * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_OFFLINE. + * + * When 'offline' changes from true to false, then after + * network services have been restarted, we send a notification + * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_ONLINE. + */ +#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline" +#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed" +#define NS_IOSERVICE_OFFLINE "offline" +#define NS_IOSERVICE_ONLINE "online" + +%} + +[builtinclass, uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)] +interface nsIIOServiceInternal : nsISupports +{ + /** + * This is an internal method that should only be called from ContentChild + * in order to pass the connectivity state from the chrome process to the + * content process. It throws if called outside the content process. + */ + void SetConnectivity(in boolean connectivity); + + /** + * An internal method to asynchronously run our notifications that happen + * when we wake from sleep + */ + void NotifyWakeup(); +}; diff --git a/netwerk/base/nsIIOService2.idl b/netwerk/base/nsIIOService2.idl new file mode 100644 index 000000000..d7b434d17 --- /dev/null +++ b/netwerk/base/nsIIOService2.idl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIIOService.idl" + +interface nsIDOMNode; +interface nsIPrincipal; + +/** + * nsIIOService2 extends nsIIOService + */ +[scriptable, uuid(52c5804b-0d3c-4d4f-8654-1c36fd310e69)] +interface nsIIOService2 : nsIIOService +{ + /** + * While this is set, IOService will monitor an nsINetworkLinkService + * (if available) and set its offline status to "true" whenever + * isLinkUp is false. + * + * Applications that want to control changes to the IOService's offline + * status should set this to false, watch for network:link-status-changed + * broadcasts, and change nsIIOService::offline as they see fit. Note + * that this means during application startup, IOService may be offline + * if there is no link, until application code runs and can turn off + * this management. + */ + attribute boolean manageOfflineStatus; + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aProxyURI + * nsIURI to use for proxy resolution. Can be null in which + * case aURI is used + * @param aProxyFlags flags from nsIProtocolProxyService to use + * when resolving proxies for this new channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + */ + nsIChannel newChannelFromURIWithProxyFlags2(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * ***** DEPRECATED ***** + * Please use newChannelFromURIWithProxyFlags2() + * + * Creates a channel for a given URI. + * + * @param aURI nsIURI from which to make a channel + * @param aProxyURI nsIURI to use for proxy resolution. Can be null in which + * case aURI is used + * @param aProxyFlags flags from nsIProtocolProxyService to use + * when resolving proxies for this new channel + * @return reference to the new nsIChannel object + */ + nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags); + +}; diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl new file mode 100644 index 000000000..3ae363c48 --- /dev/null +++ b/netwerk/base/nsIIncrementalDownload.idl @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIURI; +interface nsIFile; +interface nsIRequestObserver; + +/** + * An incremental download object attempts to fetch a file piecemeal over time + * in an effort to minimize network bandwidth usage. + * + * Canceling a background download does not cause the file on disk to be + * deleted. + */ +[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)] +interface nsIIncrementalDownload : nsIRequest +{ + /** + * Initialize the incremental download object. If the destination file + * already exists, then only the remaining portion of the file will be + * fetched. + * + * NOTE: The downloader will create the destination file if it does not + * already exist. It will create the file with the permissions 0600 if + * needed. To affect the permissions of the file, consumers of this + * interface may create an empty file at the specified destination prior to + * starting the incremental download. + * + * NOTE: Since this class may create a temporary file at the specified + * destination, it is advisable for the consumer of this interface to specify + * a file name for the destination that would not tempt the user into + * double-clicking it. For example, it might be wise to append a file + * extension like ".part" to the end of the destination to protect users from + * accidentally running "blah.exe" before it is a complete file. + * + * @param uri + * The URI to fetch. + * @param destination + * The location where the file is to be stored. + * @param chunkSize + * The size of the chunks to fetch. A non-positive value results in + * the default chunk size being used. + * @param intervalInSeconds + * The amount of time to wait between fetching chunks. Pass a + * negative to use the default interval, or 0 to fetch the remaining + * part of the file in one chunk. + */ + void init(in nsIURI uri, in nsIFile destination, in long chunkSize, + in long intervalInSeconds); + + /** + * The URI being fetched. + */ + readonly attribute nsIURI URI; + + /** + * The URI being fetched after any redirects have been followed. This + * attribute is set just prior to calling OnStartRequest on the observer + * passed to the start method. + */ + readonly attribute nsIURI finalURI; + + /** + * The file where the download is being written. + */ + readonly attribute nsIFile destination; + + /** + * The total number of bytes for the requested file. This attribute is set + * just prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the total size is unknown. + */ + readonly attribute long long totalSize; + + /** + * The current number of bytes downloaded so far. This attribute is set just + * prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the current size is unknown. + */ + readonly attribute long long currentSize; + + /** + * Start the incremental download. + * + * @param observer + * An observer to be notified of various events. OnStartRequest is + * called when finalURI and totalSize have been determined or when an + * error occurs. OnStopRequest is called when the file is completely + * downloaded or when an error occurs. If this object implements + * nsIProgressEventSink, then its OnProgress method will be called as + * data is written to the destination file. If this object implements + * nsIInterfaceRequestor, then it will be assigned as the underlying + * channel's notification callbacks, which allows it to provide a + * nsIAuthPrompt implementation if needed by the channel, for example. + * @param ctxt + * User defined object forwarded to the observer's methods. + */ + void start(in nsIRequestObserver observer, + in nsISupports ctxt); +}; diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl new file mode 100644 index 000000000..60aa9cfef --- /dev/null +++ b/netwerk/base/nsIIncrementalStreamLoader.idl @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +interface nsIRequest; +interface nsIIncrementalStreamLoader; + +[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)] +interface nsIIncrementalStreamLoaderObserver : nsISupports +{ + /** + * Called when new data has arrived on the stream. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param dataLength the length of the new data received + * @param data the contents of the new data received. + * + * This method will always be called asynchronously by the + * nsIIncrementalStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to not accumulate all or portional of the data in + * the internal buffer, the consumedLength shall be set to the value of + * the dataLength or less. By default the consumedLength value is assumed 0. + * The data and dataLength reflect the non-consumed data and will be + * accumulated if consumedLength is not set. + * + * In comparison with onStreamComplete(), the data buffer cannot be + * adopted if this method returns NS_SUCCESS_ADOPTED_DATA. + */ + void onIncrementalData(in nsIIncrementalStreamLoader loader, + in nsISupports ctxt, + in unsigned long dataLength, + [const,array,size_is(dataLength)] in octet data, + inout unsigned long consumedLength); + + /** + * Called when the entire stream has been loaded. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param status the status of the underlying channel + * @param resultLength the length of the data loaded + * @param result the data + * + * This method will always be called asynchronously by the + * nsIIncrementalStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to take over responsibility for the + * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA + * in place of NS_OK as its success code. The loader will then + * "forget" about the data and not free() it after + * onStreamComplete() returns; observer must call free() + * when the data is no longer required. + */ + void onStreamComplete(in nsIIncrementalStreamLoader loader, + in nsISupports ctxt, + in nsresult status, + in unsigned long resultLength, + [const,array,size_is(resultLength)] in octet result); +}; + +/** + * Asynchronously loads a channel into a memory buffer. + * + * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver + * that will be notified when the data has been loaded. Then call asyncOpen() + * on the channel with the nsIIncrementalStreamLoader as the listener. The context + * argument in the asyncOpen() call will be passed to the onStreamComplete() + * callback. + * + * XXX define behaviour for sizes >4 GB + */ +[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)] +interface nsIIncrementalStreamLoader : nsIStreamListener +{ + /** + * Initialize this stream loader, and start loading the data. + * + * @param aObserver + * An observer that will be notified when the data is complete. + */ + void init(in nsIIncrementalStreamLoaderObserver aObserver); + + /** + * Gets the number of bytes read so far. + */ + readonly attribute unsigned long numBytesRead; + + /** + * Gets the request that loaded this file. + * null after the request has finished loading. + */ + readonly attribute nsIRequest request; +}; diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl new file mode 100644 index 000000000..3af16ed62 --- /dev/null +++ b/netwerk/base/nsIInputStreamChannel.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIURI; + +/** + * nsIInputStreamChannel + * + * This interface provides methods to initialize an input stream channel. + * The input stream channel serves as a data pump for an input stream. + */ +[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)] +interface nsIInputStreamChannel : nsISupports +{ + /** + * Sets the URI for this channel. This must be called before the + * channel is opened, and it may only be called once. + */ + void setURI(in nsIURI aURI); + + /** + * Get/set the content stream + * + * This stream contains the data that will be pushed to the channel's + * stream listener. If the stream is non-blocking and supports the + * nsIAsyncInputStream interface, then the stream will be read directly. + * Otherwise, the stream will be read on a background thread. + * + * This attribute must be set before the channel is opened, and it may + * only be set once. + * + * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel + * has been opened. + */ + attribute nsIInputStream contentStream; + + /** + * Get/set the srcdoc data string. When the input stream channel is + * created to load a srcdoc iframe, this is set to hold the value of the + * srcdoc attribute. + * + * This should be the same value used to create contentStream, but this is + * not checked. + * + * Changing the value of this attribute will not otherwise affect the + * functionality of the channel or input stream. + */ + attribute AString srcdocData; + + /** + * Returns true if srcdocData has been set within the channel. + */ + readonly attribute boolean isSrcdocChannel; + + /** + * The base URI to be used for the channel. Used when the base URI cannot + * be inferred by other means, for example when this is a srcdoc channel. + */ + attribute nsIURI baseURI; +}; diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl new file mode 100644 index 000000000..83c29cdbb --- /dev/null +++ b/netwerk/base/nsIInputStreamPump.idl @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIStreamListener; + +/** + * nsIInputStreamPump + * + * This interface provides a means to configure and use a input stream pump + * instance. The input stream pump will asynchronously read from an input + * stream, and push data to an nsIStreamListener instance. It utilizes the + * current thread's nsIEventTarget in order to make reading from the stream + * asynchronous. A different thread can be used if the pump also implements + * nsIThreadRetargetableRequest. + * + * If the given stream supports nsIAsyncInputStream, then the stream pump will + * call the stream's AsyncWait method to drive the stream listener. Otherwise, + * the stream will be read on a background thread utilizing the stream + * transport service. More details are provided below. + */ +[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)] +interface nsIInputStreamPump : nsIRequest +{ + /** + * Initialize the input stream pump. + * + * @param aStream + * contains the data to be read. if the input stream is non-blocking, + * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds + * then the stream will be read directly. otherwise, it will be read + * on a background thread using the stream transport service. + * @param aStreamPos + * specifies the stream offset from which to start reading. the + * offset value is absolute. pass -1 to specify the current offset. + * NOTE: this parameter is ignored if the underlying stream does not + * implement nsISeekableStream. + * @param aStreamLen + * specifies how much data to read from the stream. pass -1 to read + * all data available in the stream. + * @param aSegmentSize + * if the stream transport service is used, then this parameter + * specifies the segment size for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aSegmentCount + * if the stream transport service is used, then this parameter + * specifies the segment count for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aCloseWhenDone + * if true, the input stream will be closed after it has been read. + */ + void init(in nsIInputStream aStream, + in long long aStreamPos, + in long long aStreamLen, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount, + in boolean aCloseWhenDone); + + /** + * asyncRead causes the input stream to be read in chunks and delivered + * asynchronously to the listener via OnDataAvailable. + * + * @param aListener + * receives notifications. + * @param aListenerContext + * passed to listener methods. + */ + void asyncRead(in nsIStreamListener aListener, + in nsISupports aListenerContext); +}; diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl new file mode 100644 index 000000000..b344a1544 --- /dev/null +++ b/netwerk/base/nsILoadContextInfo.idl @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#include "mozilla/BasePrincipal.h" +%} +native OriginAttributesNativePtr(const mozilla::NeckoOriginAttributes*); + +interface nsILoadContext; +interface nsIDOMWindow; + +/** + * Helper interface to carry informatin about the load context + * encapsulating origin attributes and IsAnonymous, IsPrivite properties. + * It shall be used where nsILoadContext cannot be used or is not + * available. + */ + +[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)] +interface nsILoadContextInfo : nsISupports +{ + const unsigned long NO_APP_ID = 0; + const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX + + /** + * Whether the context is in a Private Browsing mode + */ + readonly attribute boolean isPrivate; + + /** + * Whether the load is initiated as anonymous + */ + readonly attribute boolean isAnonymous; + + /** + * NeckoOriginAttributes hiding all the security context attributes + */ + [implicit_jscontext] + readonly attribute jsval originAttributes; + [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)] + OriginAttributesNativePtr binaryOriginAttributesPtr(); + +%{C++ + /** + * De-XPCOMed getters + */ + bool IsPrivate() + { + bool pb; + GetIsPrivate(&pb); + return pb; + } + + bool IsAnonymous() + { + bool anon; + GetIsAnonymous(&anon); + return anon; + } + + bool Equals(nsILoadContextInfo *aOther) + { + return IsAnonymous() == aOther->IsAnonymous() && + *OriginAttributesPtr() == *aOther->OriginAttributesPtr(); + } +%} +}; + +/** + * Since NeckoOriginAttributes struct limits the implementation of + * nsILoadContextInfo (that needs to be thread safe) to C++, + * we need a scriptable factory to create instances of that + * interface from JS. + */ +[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)] +interface nsILoadContextInfoFactory : nsISupports +{ + readonly attribute nsILoadContextInfo default; + readonly attribute nsILoadContextInfo private; + readonly attribute nsILoadContextInfo anonymous; + [implicit_jscontext] + nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes); + nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous); + nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous); +}; diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl new file mode 100644 index 000000000..4f89bd0e3 --- /dev/null +++ b/netwerk/base/nsILoadGroup.idl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsISimpleEnumerator; +interface nsIRequestObserver; +interface nsIInterfaceRequestor; +interface nsIRequestContext; + +/** + * A load group maintains a collection of nsIRequest objects. + * This is used in lots of places where groups of requests need to be tracked. + * For example, nsIDocument::mDocumentLoadGroup is used to track all requests + * made for subdocuments in order to track page load progress and allow all + * requests made on behalf of the document to be stopped, etc. + */ +[scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)] +interface nsILoadGroup : nsIRequest +{ + /** + * The group observer is notified when requests are added to and removed + * from this load group. The groupObserver is weak referenced. + */ + attribute nsIRequestObserver groupObserver; + + /** + * Accesses the default load request for the group. Each time a number + * of requests are added to a group, the defaultLoadRequest may be set + * to indicate that all of the requests are related to a base request. + * + * The load group inherits its load flags from the default load request. + * If the default load request is NULL, then the group's load flags are + * not changed. + */ + attribute nsIRequest defaultLoadRequest; + + /** + * Adds a new request to the group. This will cause the default load + * flags to be applied to the request. If this is a foreground + * request then the groupObserver's onStartRequest will be called. + * + * If the request is the default load request or if the default load + * request is null, then the load group will inherit its load flags from + * the request. + */ + void addRequest(in nsIRequest aRequest, + in nsISupports aContext); + + /** + * Removes a request from the group. If this is a foreground request + * then the groupObserver's onStopRequest will be called. + * + * By the time this call ends, aRequest will have been removed from the + * loadgroup, even if this function throws an exception. + */ + void removeRequest(in nsIRequest aRequest, + in nsISupports aContext, + in nsresult aStatus); + + /** + * Returns the requests contained directly in this group. + * Enumerator element type: nsIRequest. + */ + readonly attribute nsISimpleEnumerator requests; + + /** + * Returns the count of "active" requests (ie. requests without the + * LOAD_BACKGROUND bit set). + */ + readonly attribute unsigned long activeCount; + + /** + * Notification callbacks for the load group. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Context for managing things like js/css connection blocking, + * and per-tab connection grouping. + */ + [noscript] readonly attribute nsID requestContextID; + + /** + * The set of load flags that will be added to all new requests added to + * this group. Any existing requests in the load group are not modified, + * so it is expected these flags will be added before requests are added + * to the group - typically via nsIDocShell::defaultLoadFlags on a new + * docShell. + * Note that these flags are *not* added to the default request for the + * load group; it is expected the default request will already have these + * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before + * the docShell has created the default request.) + */ + attribute nsLoadFlags defaultLoadFlags; + + /** + * The cached user agent override created by UserAgentOverrides.jsm. Used + * for all sub-resource requests in the loadgroup. + */ + attribute ACString userAgentOverrideCache; +}; diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl new file mode 100644 index 000000000..3ec60a1a2 --- /dev/null +++ b/netwerk/base/nsILoadGroupChild.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsILoadGroup; + +/** + * nsILoadGroupChild provides a hierarchy of load groups so that the + * root load group can be used to conceptually tie a series of loading + * operations into a logical whole while still leaving them separate + * for the purposes of cancellation and status events. + */ + +[scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)] +interface nsILoadGroupChild : nsISupports +{ + /** + * The parent of this load group. It is stored with + * a nsIWeakReference/nsWeakPtr so there is no requirement for the + * parentLoadGroup to out live the child, nor will the child keep a + * reference count on the parent. + */ + attribute nsILoadGroup parentLoadGroup; + + /** + * The nsILoadGroup associated with this nsILoadGroupChild + */ + readonly attribute nsILoadGroup childLoadGroup; + + /** + * The rootLoadGroup is the recursive parent of this + * load group where parent is defined as parentlLoadGroup if set + * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes + * precedence.) The nsILoadGroup child is the root if neither parent + * nor loadgroup attribute is specified. + */ + readonly attribute nsILoadGroup rootLoadGroup; +}; + diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl new file mode 100644 index 000000000..78433c8b8 --- /dev/null +++ b/netwerk/base/nsILoadInfo.idl @@ -0,0 +1,732 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsIDOMDocument; +interface nsINode; +interface nsIPrincipal; + +%{C++ +#include "nsTArray.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadTainting.h" + +class nsCString; +%} + +[ref] native const_nsIPrincipalArray(const nsTArray>); +native NeckoOriginAttributes(mozilla::NeckoOriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes); +[ref] native StringArrayRef(const nsTArray); + +typedef unsigned long nsSecurityFlags; + +/** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ +[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)] +interface nsILoadInfo : nsISupports +{ + /** + * *** DEPRECATED *** + * No LoadInfo created within Gecko should contain this security flag. + * Please use any of the five security flags defined underneath. + * We only keep this security flag to provide backwards compatibilty. + */ + const unsigned long SEC_NORMAL = 0; + + /** + * The following five flags determine the security mode and hence what kind of + * security checks should be performed throughout the lifetime of the channel. + * + * * SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS + * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + * * SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS + * * SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL + * * SEC_REQUIRE_CORS_DATA_INHERITS + * + * Exactly one of these flags are required to be set in order to allow + * the channel to perform the correct security checks (SOP, CORS, ...) and + * return the correct result principal. If none or more than one of these + * flags are set AsyncOpen2 will fail. + */ + + /* + * Enforce the same origin policy where data: loads inherit + * the principal. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS = (1<<0); + + /* + * Enforce the same origin policy but data: loads are blocked. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1); + + /** + * Allow loads from other origins. Loads from data: will inherit + * the principal of the origin that triggered the load. + * Commonly used by plain ,